cortexhawk 3.2.0 → 3.3.1
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 +45 -0
- package/CLAUDE.md +12 -4
- package/agents/git-manager.md +6 -2
- package/commands/backlog.md +1 -1
- package/commands/cleanup.md +37 -0
- package/commands/review-pr.md +31 -0
- package/commands/ship.md +1 -0
- package/commands/task.md +1 -1
- package/cortexhawk +9 -3
- package/hooks/branch-guard.sh +8 -1
- package/hooks/codex-dispatcher.sh +3 -0
- 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 +159 -962
- package/mcp/README.md +36 -0
- package/mcp/context7.json +1 -1
- package/mcp/github.json +11 -0
- package/mcp/puppeteer.json +1 -1
- package/mcp/sequential-thinking.json +1 -1
- 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/doctor.sh +164 -0
- package/scripts/install-claude.sh +179 -0
- package/scripts/interactive-init.sh +3 -2
- package/scripts/lint-guard-runner.sh +132 -0
- package/scripts/post-merge-cleanup.sh +233 -0
- package/scripts/restore.sh +212 -0
- package/scripts/snapshot.sh +163 -0
- package/scripts/update.sh +280 -0
- package/settings.json +12 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# update.sh — CortexHawk update flow
|
|
3
|
+
# Sourced by install.sh when --update is used
|
|
4
|
+
# Uses shared functions: detect_source_type, get_version, do_snapshot, update_source_git,
|
|
5
|
+
# update_source_release, compute_checksum, sync_all_components, generate_hooks_config,
|
|
6
|
+
# write_manifest, run_audit, update_gitignore, setup_templates, cleanup_update
|
|
7
|
+
|
|
8
|
+
do_update() {
|
|
9
|
+
# 1. Validate target
|
|
10
|
+
if [ "$TARGET_CLI" != "claude" ]; then
|
|
11
|
+
echo "Error: --update is currently supported for Claude Code only"
|
|
12
|
+
echo "For Kimi CLI, re-run: install.sh --target kimi"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ "$GLOBAL" = true ]; then
|
|
17
|
+
TARGET="$HOME/.claude"
|
|
18
|
+
else
|
|
19
|
+
TARGET="$(pwd)/.claude"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if [ ! -d "$TARGET" ]; then
|
|
23
|
+
echo "Error: no CortexHawk installation found at $TARGET"
|
|
24
|
+
echo "Run install.sh without --update for a fresh install"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# 2. Read manifest
|
|
29
|
+
local manifest="$TARGET/.cortexhawk-manifest"
|
|
30
|
+
local current_version="unknown"
|
|
31
|
+
local current_profile="all"
|
|
32
|
+
local source_type
|
|
33
|
+
source_type=$(detect_source_type)
|
|
34
|
+
|
|
35
|
+
if [ -f "$manifest" ]; then
|
|
36
|
+
current_version=$(grep '"version"' "$manifest" | sed 's/.*: *"\([^"]*\)".*/\1/')
|
|
37
|
+
current_profile=$(grep '"profile"' "$manifest" | sed 's/.*: *"\([^"]*\)".*/\1/')
|
|
38
|
+
local manifest_source
|
|
39
|
+
manifest_source=$(grep '"source"' "$manifest" | head -1 | sed 's/.*: *"\([^"]*\)".*/\1/')
|
|
40
|
+
# Only trust manifest's "git" source if SCRIPT_DIR is actually a git repo
|
|
41
|
+
if [ -n "$manifest_source" ]; then
|
|
42
|
+
if [ "$manifest_source" = "git" ] && git -C "$SCRIPT_DIR" rev-parse --git-dir >/dev/null 2>&1; then
|
|
43
|
+
source_type="git"
|
|
44
|
+
elif [ "$manifest_source" != "git" ]; then
|
|
45
|
+
source_type="$manifest_source"
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
else
|
|
49
|
+
echo " No manifest found — treating as pre-update installation"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# 3. Profile override
|
|
53
|
+
local update_profile="$current_profile"
|
|
54
|
+
if [ -n "$PROFILE" ]; then
|
|
55
|
+
update_profile="$PROFILE"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if [ "$DRY_RUN" = true ]; then
|
|
59
|
+
echo "CortexHawk Dry Run (update)"
|
|
60
|
+
echo "============================"
|
|
61
|
+
else
|
|
62
|
+
echo "CortexHawk Update"
|
|
63
|
+
echo "==================="
|
|
64
|
+
fi
|
|
65
|
+
echo " Current version: $current_version"
|
|
66
|
+
echo " Profile: $update_profile"
|
|
67
|
+
echo " Source: $source_type"
|
|
68
|
+
echo ""
|
|
69
|
+
|
|
70
|
+
# 3b. Auto-snapshot before update (skip in dry-run)
|
|
71
|
+
if [ "$DRY_RUN" != true ] && [ -f "$manifest" ]; then
|
|
72
|
+
echo "Creating pre-update snapshot..."
|
|
73
|
+
do_snapshot || echo " Warning: pre-update snapshot failed — continuing without rollback point"
|
|
74
|
+
PRE_UPDATE_SNAP=$(ls -t "$TARGET/.cortexhawk-snapshots"/*.json 2>/dev/null | head -1)
|
|
75
|
+
[ -n "$PRE_UPDATE_SNAP" ] && echo " Saved: $(basename "$PRE_UPDATE_SNAP")"
|
|
76
|
+
echo ""
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# 4. Pull source (skip in dry-run — compare against current source)
|
|
80
|
+
if [ "$DRY_RUN" != true ]; then
|
|
81
|
+
if [ "$source_type" = "git" ]; then
|
|
82
|
+
echo "Updating CortexHawk source via git pull..."
|
|
83
|
+
update_source_git
|
|
84
|
+
else
|
|
85
|
+
echo "Updating CortexHawk source via download..."
|
|
86
|
+
update_source_release
|
|
87
|
+
fi
|
|
88
|
+
echo " Source updated successfully."
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# 5. Compare versions
|
|
92
|
+
local new_version
|
|
93
|
+
new_version=$(get_version)
|
|
94
|
+
echo " New version: $new_version"
|
|
95
|
+
echo ""
|
|
96
|
+
|
|
97
|
+
if [ "$current_version" = "$new_version" ] && [ "$FORCE_MODE" != true ]; then
|
|
98
|
+
# Same version — check if files actually changed (checksum comparison)
|
|
99
|
+
local files_changed=0
|
|
100
|
+
if [ -f "$manifest" ]; then
|
|
101
|
+
while IFS= read -r line; do
|
|
102
|
+
local fpath fhash
|
|
103
|
+
fpath=$(echo "$line" | sed 's/.*"\([^"]*\)": "sha256:\([^"]*\)".*/\1/')
|
|
104
|
+
fhash=$(echo "$line" | sed 's/.*"sha256:\([^"]*\)".*/\1/')
|
|
105
|
+
[ -z "$fpath" ] || [ -z "$fhash" ] && continue
|
|
106
|
+
local source_file="$SCRIPT_DIR/$fpath"
|
|
107
|
+
[ -f "$source_file" ] || continue
|
|
108
|
+
local source_hash
|
|
109
|
+
source_hash=$(compute_checksum "$source_file")
|
|
110
|
+
if [ "$fhash" != "$source_hash" ]; then
|
|
111
|
+
files_changed=$((files_changed + 1))
|
|
112
|
+
fi
|
|
113
|
+
done < <(grep '"sha256:' "$manifest")
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [ "$files_changed" -eq 0 ] && [ "$DRY_RUN" != true ]; then
|
|
117
|
+
# Still apply install improvements even if no component files changed
|
|
118
|
+
local target_dir_name=".${TARGET_CLI:-claude}"
|
|
119
|
+
update_gitignore "$(dirname "$TARGET")" "$target_dir_name"
|
|
120
|
+
[ "$TARGET_CLI" = "codex" ] && update_gitignore "$(dirname "$TARGET")" ".agents"
|
|
121
|
+
if [ ! -f "$TARGET/git-workflow.conf" ]; then
|
|
122
|
+
GIT_BRANCHING="direct-main"
|
|
123
|
+
GIT_COMMIT_CONVENTION="conventional"
|
|
124
|
+
GIT_PR_PREFERENCE="on-demand"
|
|
125
|
+
GIT_AUTO_PUSH="after-commit"
|
|
126
|
+
GIT_WORK_BRANCH=""
|
|
127
|
+
source "$SCRIPT_DIR/scripts/git-workflow-init.sh" "$(dirname "$TARGET")" "$TARGET"
|
|
128
|
+
fi
|
|
129
|
+
echo "Already up to date ($new_version). No component files changed."
|
|
130
|
+
cleanup_update
|
|
131
|
+
exit 0
|
|
132
|
+
elif [ "$files_changed" -gt 0 ]; then
|
|
133
|
+
echo " Same version ($new_version) but $files_changed file(s) changed in source."
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [ "$DRY_RUN" = true ]; then
|
|
138
|
+
echo " Comparing source $new_version vs installed $current_version"
|
|
139
|
+
elif [ "$current_version" = "$new_version" ]; then
|
|
140
|
+
echo " Syncing changed files ($new_version)..."
|
|
141
|
+
else
|
|
142
|
+
echo " Updating $current_version -> $new_version"
|
|
143
|
+
fi
|
|
144
|
+
echo ""
|
|
145
|
+
|
|
146
|
+
# Set up profile file for skill filtering
|
|
147
|
+
if [ -n "$update_profile" ] && [ "$update_profile" != "all" ]; then
|
|
148
|
+
PROFILE_FILE="$SCRIPT_DIR/profiles/${update_profile}.json"
|
|
149
|
+
if [ ! -f "$PROFILE_FILE" ]; then
|
|
150
|
+
echo " Warning: profile '$update_profile' not found in source — installing all skills"
|
|
151
|
+
update_profile="all"
|
|
152
|
+
PROFILE_FILE=""
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
# 6. Reset counters and sync components
|
|
157
|
+
SYNC_ADDED=0
|
|
158
|
+
SYNC_UPDATED=0
|
|
159
|
+
SYNC_UNCHANGED=0
|
|
160
|
+
SYNC_SKIPPED=0
|
|
161
|
+
SYNC_CONFLICTS=0
|
|
162
|
+
|
|
163
|
+
sync_all_components "$update_profile"
|
|
164
|
+
|
|
165
|
+
# 6b. Sync agent personas from project root
|
|
166
|
+
local project_root
|
|
167
|
+
project_root="$(dirname "$TARGET")"
|
|
168
|
+
if [ -d "$project_root/.cortexhawk-agents" ] && [ "$DRY_RUN" != true ]; then
|
|
169
|
+
cp -r "$project_root/.cortexhawk-agents/"*.md "$TARGET/agents/" 2>/dev/null || true
|
|
170
|
+
local pc
|
|
171
|
+
pc=$(find "$project_root/.cortexhawk-agents" -name "*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
172
|
+
[ "$pc" -gt 0 ] && echo " Synced $pc agent persona(s) from .cortexhawk-agents/"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# 7. Detect removed upstream files
|
|
176
|
+
if [ -f "$manifest" ]; then
|
|
177
|
+
while IFS= read -r line; do
|
|
178
|
+
local file_relpath
|
|
179
|
+
file_relpath=$(echo "$line" | sed 's/.*"\([^"]*\)": "sha256:.*/\1/')
|
|
180
|
+
[ -z "$file_relpath" ] && continue
|
|
181
|
+
if [ ! -f "$SCRIPT_DIR/$file_relpath" ] && [ -f "$TARGET/$file_relpath" ]; then
|
|
182
|
+
echo " Warning: $file_relpath was removed upstream (kept locally)"
|
|
183
|
+
fi
|
|
184
|
+
done < <(grep '"sha256:' "$manifest")
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
if [ "$DRY_RUN" != true ]; then
|
|
188
|
+
# Make executable components executable
|
|
189
|
+
for entry in "${COMPONENTS[@]}"; do
|
|
190
|
+
IFS=':' read -r name exec_flag <<< "$entry"
|
|
191
|
+
[ "$exec_flag" = "yes" ] && chmod +x "$TARGET/$name/"*.sh 2>/dev/null || true
|
|
192
|
+
done
|
|
193
|
+
|
|
194
|
+
# 7b. Regenerate settings.json hooks + merge new permissions from compose.yml
|
|
195
|
+
if [ -f "$SCRIPT_DIR/hooks/compose.yml" ]; then
|
|
196
|
+
local hooks_json
|
|
197
|
+
hooks_json=$(generate_hooks_config "$SCRIPT_DIR/hooks/compose.yml" ".claude/hooks")
|
|
198
|
+
if [ -n "$hooks_json" ] && [ "$hooks_json" != "{}" ] && command -v python3 >/dev/null 2>&1; then
|
|
199
|
+
echo "$hooks_json" | python3 -c "
|
|
200
|
+
import json, sys
|
|
201
|
+
hooks = json.load(sys.stdin)
|
|
202
|
+
current = {}
|
|
203
|
+
try:
|
|
204
|
+
with open(sys.argv[1]) as f:
|
|
205
|
+
current = json.load(f)
|
|
206
|
+
except:
|
|
207
|
+
pass
|
|
208
|
+
current['hooks'] = hooks
|
|
209
|
+
# Merge new permissions from source
|
|
210
|
+
try:
|
|
211
|
+
with open(sys.argv[2]) as f:
|
|
212
|
+
src_perms = json.load(f).get('permissions', {})
|
|
213
|
+
cur_perms = current.get('permissions', {})
|
|
214
|
+
for key in ('allow', 'deny'):
|
|
215
|
+
src_list = src_perms.get(key, [])
|
|
216
|
+
cur_list = cur_perms.get(key, [])
|
|
217
|
+
added = [p for p in src_list if p not in cur_list]
|
|
218
|
+
if added:
|
|
219
|
+
cur_list.extend(added)
|
|
220
|
+
cur_perms[key] = cur_list
|
|
221
|
+
current['permissions'] = cur_perms
|
|
222
|
+
except:
|
|
223
|
+
pass
|
|
224
|
+
with open(sys.argv[1], 'w') as f:
|
|
225
|
+
json.dump(current, f, indent=2)
|
|
226
|
+
f.write('\n')
|
|
227
|
+
" "$TARGET/settings.json" "$SCRIPT_DIR/settings.json"
|
|
228
|
+
echo " Regenerated settings.json hooks from compose.yml"
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
# 8. Write new manifest
|
|
233
|
+
write_manifest "$TARGET" "$update_profile" "$TARGET_CLI" true
|
|
234
|
+
|
|
235
|
+
# 9. Run audit
|
|
236
|
+
run_audit "$(dirname "$TARGET")"
|
|
237
|
+
|
|
238
|
+
# 10. Apply install improvements (gitignore, git-workflow defaults)
|
|
239
|
+
local target_dir_name=".${TARGET_CLI:-claude}"
|
|
240
|
+
update_gitignore "$(dirname "$TARGET")" "$target_dir_name"
|
|
241
|
+
[ "$TARGET_CLI" = "codex" ] && update_gitignore "$(dirname "$TARGET")" ".agents"
|
|
242
|
+
setup_templates "$(dirname "$TARGET")"
|
|
243
|
+
|
|
244
|
+
if [ ! -f "$TARGET/git-workflow.conf" ]; then
|
|
245
|
+
GIT_BRANCHING="direct-main"
|
|
246
|
+
GIT_COMMIT_CONVENTION="conventional"
|
|
247
|
+
GIT_PR_PREFERENCE="on-demand"
|
|
248
|
+
GIT_AUTO_PUSH="after-commit"
|
|
249
|
+
GIT_WORK_BRANCH=""
|
|
250
|
+
source "$SCRIPT_DIR/scripts/git-workflow-init.sh" "$(dirname "$TARGET")" "$TARGET"
|
|
251
|
+
fi
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
# 10. Print summary
|
|
255
|
+
echo ""
|
|
256
|
+
if [ "$DRY_RUN" = true ]; then
|
|
257
|
+
echo "Dry run summary:"
|
|
258
|
+
echo " Would add: $SYNC_ADDED"
|
|
259
|
+
echo " Would update: $SYNC_UPDATED"
|
|
260
|
+
echo " Unchanged: $SYNC_UNCHANGED"
|
|
261
|
+
echo " Would skip: $SYNC_SKIPPED"
|
|
262
|
+
echo " Conflicts: $SYNC_CONFLICTS"
|
|
263
|
+
echo ""
|
|
264
|
+
echo "No files were modified (dry run)."
|
|
265
|
+
else
|
|
266
|
+
echo "Update complete: $current_version -> $new_version"
|
|
267
|
+
echo " Added: $SYNC_ADDED"
|
|
268
|
+
echo " Updated: $SYNC_UPDATED"
|
|
269
|
+
echo " Unchanged: $SYNC_UNCHANGED"
|
|
270
|
+
echo " Skipped: $SYNC_SKIPPED"
|
|
271
|
+
echo " Conflicts: $SYNC_CONFLICTS"
|
|
272
|
+
if [ -n "${PRE_UPDATE_SNAP:-}" ]; then
|
|
273
|
+
echo " Rollback: install.sh --restore $PRE_UPDATE_SNAP"
|
|
274
|
+
fi
|
|
275
|
+
echo ""
|
|
276
|
+
echo " To activate: exit your CLI (ctrl+c) and relaunch in this directory."
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
cleanup_update
|
|
280
|
+
}
|
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": [
|