aether-colony 5.2.1 → 5.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/.aether/aether-utils.sh +35 -0
- package/.aether/agents/aether-ambassador.md +140 -0
- package/.aether/agents/aether-archaeologist.md +108 -0
- package/.aether/agents/aether-architect.md +133 -0
- package/.aether/agents/aether-auditor.md +144 -0
- package/.aether/agents/aether-builder.md +184 -0
- package/.aether/agents/aether-chaos.md +115 -0
- package/.aether/agents/aether-chronicler.md +122 -0
- package/.aether/agents/aether-gatekeeper.md +116 -0
- package/.aether/agents/aether-includer.md +117 -0
- package/.aether/agents/aether-keeper.md +177 -0
- package/.aether/agents/aether-measurer.md +128 -0
- package/.aether/agents/aether-oracle.md +137 -0
- package/.aether/agents/aether-probe.md +133 -0
- package/.aether/agents/aether-queen.md +286 -0
- package/.aether/agents/aether-route-setter.md +130 -0
- package/.aether/agents/aether-sage.md +106 -0
- package/.aether/agents/aether-scout.md +101 -0
- package/.aether/agents/aether-surveyor-disciplines.md +391 -0
- package/.aether/agents/aether-surveyor-nest.md +329 -0
- package/.aether/agents/aether-surveyor-pathogens.md +264 -0
- package/.aether/agents/aether-surveyor-provisions.md +334 -0
- package/.aether/agents/aether-tracker.md +137 -0
- package/.aether/agents/aether-watcher.md +174 -0
- package/.aether/agents/aether-weaver.md +130 -0
- package/.aether/commands/claude/archaeology.md +334 -0
- package/.aether/commands/claude/build.md +65 -0
- package/.aether/commands/claude/chaos.md +336 -0
- package/.aether/commands/claude/colonize.md +259 -0
- package/.aether/commands/claude/continue.md +60 -0
- package/.aether/commands/claude/council.md +507 -0
- package/.aether/commands/claude/data-clean.md +81 -0
- package/.aether/commands/claude/dream.md +268 -0
- package/.aether/commands/claude/entomb.md +498 -0
- package/.aether/commands/claude/export-signals.md +57 -0
- package/.aether/commands/claude/feedback.md +96 -0
- package/.aether/commands/claude/flag.md +151 -0
- package/.aether/commands/claude/flags.md +169 -0
- package/.aether/commands/claude/focus.md +76 -0
- package/.aether/commands/claude/help.md +154 -0
- package/.aether/commands/claude/history.md +140 -0
- package/.aether/commands/claude/import-signals.md +71 -0
- package/.aether/commands/claude/init.md +505 -0
- package/.aether/commands/claude/insert-phase.md +105 -0
- package/.aether/commands/claude/interpret.md +278 -0
- package/.aether/commands/claude/lay-eggs.md +210 -0
- package/.aether/commands/claude/maturity.md +113 -0
- package/.aether/commands/claude/memory-details.md +77 -0
- package/.aether/commands/claude/migrate-state.md +171 -0
- package/.aether/commands/claude/oracle.md +642 -0
- package/.aether/commands/claude/organize.md +232 -0
- package/.aether/commands/claude/patrol.md +620 -0
- package/.aether/commands/claude/pause-colony.md +233 -0
- package/.aether/commands/claude/phase.md +115 -0
- package/.aether/commands/claude/pheromones.md +156 -0
- package/.aether/commands/claude/plan.md +693 -0
- package/.aether/commands/claude/preferences.md +65 -0
- package/.aether/commands/claude/quick.md +100 -0
- package/.aether/commands/claude/redirect.md +76 -0
- package/.aether/commands/claude/resume-colony.md +197 -0
- package/.aether/commands/claude/resume.md +388 -0
- package/.aether/commands/claude/run.md +231 -0
- package/.aether/commands/claude/seal.md +774 -0
- package/.aether/commands/claude/skill-create.md +286 -0
- package/.aether/commands/claude/status.md +410 -0
- package/.aether/commands/claude/swarm.md +349 -0
- package/.aether/commands/claude/tunnels.md +426 -0
- package/.aether/commands/claude/update.md +132 -0
- package/.aether/commands/claude/verify-castes.md +143 -0
- package/.aether/commands/claude/watch.md +239 -0
- package/.aether/commands/opencode/archaeology.md +331 -0
- package/.aether/commands/opencode/build.md +1168 -0
- package/.aether/commands/opencode/chaos.md +329 -0
- package/.aether/commands/opencode/colonize.md +195 -0
- package/.aether/commands/opencode/continue.md +1436 -0
- package/.aether/commands/opencode/council.md +437 -0
- package/.aether/commands/opencode/data-clean.md +77 -0
- package/.aether/commands/opencode/dream.md +260 -0
- package/.aether/commands/opencode/entomb.md +377 -0
- package/.aether/commands/opencode/export-signals.md +54 -0
- package/.aether/commands/opencode/feedback.md +99 -0
- package/.aether/commands/opencode/flag.md +149 -0
- package/.aether/commands/opencode/flags.md +167 -0
- package/.aether/commands/opencode/focus.md +73 -0
- package/.aether/commands/opencode/help.md +157 -0
- package/.aether/commands/opencode/history.md +136 -0
- package/.aether/commands/opencode/import-signals.md +68 -0
- package/.aether/commands/opencode/init.md +518 -0
- package/.aether/commands/opencode/insert-phase.md +111 -0
- package/.aether/commands/opencode/interpret.md +272 -0
- package/.aether/commands/opencode/lay-eggs.md +213 -0
- package/.aether/commands/opencode/maturity.md +108 -0
- package/.aether/commands/opencode/memory-details.md +83 -0
- package/.aether/commands/opencode/migrate-state.md +165 -0
- package/.aether/commands/opencode/oracle.md +593 -0
- package/.aether/commands/opencode/organize.md +226 -0
- package/.aether/commands/opencode/patrol.md +626 -0
- package/.aether/commands/opencode/pause-colony.md +203 -0
- package/.aether/commands/opencode/phase.md +113 -0
- package/.aether/commands/opencode/pheromones.md +162 -0
- package/.aether/commands/opencode/plan.md +684 -0
- package/.aether/commands/opencode/preferences.md +71 -0
- package/.aether/commands/opencode/quick.md +91 -0
- package/.aether/commands/opencode/redirect.md +84 -0
- package/.aether/commands/opencode/resume-colony.md +190 -0
- package/.aether/commands/opencode/resume.md +394 -0
- package/.aether/commands/opencode/run.md +237 -0
- package/.aether/commands/opencode/seal.md +452 -0
- package/.aether/commands/opencode/skill-create.md +63 -0
- package/.aether/commands/opencode/status.md +307 -0
- package/.aether/commands/opencode/swarm.md +15 -0
- package/.aether/commands/opencode/tunnels.md +400 -0
- package/.aether/commands/opencode/update.md +127 -0
- package/.aether/commands/opencode/verify-castes.md +139 -0
- package/.aether/commands/opencode/watch.md +227 -0
- package/.aether/docs/command-playbooks/build-full.md +1 -1
- package/.aether/docs/command-playbooks/build-prep.md +10 -3
- package/.aether/docs/command-playbooks/build-verify.md +51 -0
- package/.aether/docs/command-playbooks/continue-advance.md +115 -6
- package/.aether/docs/command-playbooks/continue-verify.md +32 -0
- package/.aether/utils/clash-detect.sh +239 -0
- package/.aether/utils/hooks/clash-pre-tool-use.js +99 -0
- package/.aether/utils/merge-driver-lockfile.sh +35 -0
- package/.aether/utils/midden.sh +534 -0
- package/.aether/utils/pheromone.sh +1376 -108
- package/.aether/utils/queen.sh +2 -4
- package/.aether/utils/state-api.sh +25 -4
- package/.aether/utils/swarm.sh +1 -1
- package/.aether/utils/worktree.sh +189 -0
- package/CHANGELOG.md +26 -0
- package/README.md +161 -161
- package/bin/cli.js +103 -61
- package/bin/lib/banner.js +14 -0
- package/bin/lib/init.js +8 -7
- package/bin/lib/interactive-setup.js +251 -0
- package/bin/npx-entry.js +21 -0
- package/bin/npx-install.js +9 -167
- package/bin/validate-package.sh +23 -0
- package/package.json +2 -2
- package/.aether/docs/plans/pheromone-display-plan.md +0 -257
- package/.aether/schemas/example-prompt-builder.xml +0 -234
- package/.aether/scripts/incident-test-add.sh +0 -47
- package/.aether/scripts/weekly-audit.sh +0 -79
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Clash detection utility -- detects file conflicts across git worktrees.
|
|
3
|
+
#
|
|
4
|
+
# When multiple agents work in parallel worktrees, they could accidentally
|
|
5
|
+
# edit the same file from different worktrees. This script checks for that.
|
|
6
|
+
#
|
|
7
|
+
# Usage: clash-detect --file <path> [--worktree <path>]
|
|
8
|
+
# Returns JSON: {ok:true, result:{conflict:false}} or
|
|
9
|
+
# {ok:true, result:{conflict:true, conflicting_worktrees:[...]}}
|
|
10
|
+
#
|
|
11
|
+
# Environment:
|
|
12
|
+
# AETHER_ROOT - repo root (auto-detected if not set)
|
|
13
|
+
# WORKTREE_DIR - current worktree path (to exclude self from checks)
|
|
14
|
+
#
|
|
15
|
+
# When sourced by aether-utils.sh, provides _clash_detect function.
|
|
16
|
+
# When run directly, executes _clash_detect with provided arguments.
|
|
17
|
+
|
|
18
|
+
# Only set strict mode when run directly (not sourced)
|
|
19
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
20
|
+
set -uo pipefail
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Set AETHER_ROOT if not already set (e.g., when sourced)
|
|
24
|
+
: "${AETHER_ROOT:=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
25
|
+
|
|
26
|
+
# Fallback error constant for standalone execution (when not sourced by aether-utils.sh)
|
|
27
|
+
: "${E_VALIDATION_FAILED:=E_VALIDATION_FAILED}"
|
|
28
|
+
|
|
29
|
+
# Allowlist of path patterns that bypass clash detection (branch-local state)
|
|
30
|
+
# These files live in .aether/data/ and are unique per worktree/branch
|
|
31
|
+
_CLASH_ALLOWLIST=(
|
|
32
|
+
".aether/data/"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Check if a file path matches the allowlist
|
|
36
|
+
_clash_is_allowlisted() {
|
|
37
|
+
local file="$1"
|
|
38
|
+
for pattern in "${_CLASH_ALLOWLIST[@]}"; do
|
|
39
|
+
if [[ "$file" == "$pattern"* ]]; then
|
|
40
|
+
return 0
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
return 1
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Normalize a path for comparison on macOS (handles /var vs /private/var)
|
|
47
|
+
_clash_normalize_path() {
|
|
48
|
+
local p="$1"
|
|
49
|
+
if [[ -z "$p" ]]; then
|
|
50
|
+
echo ""
|
|
51
|
+
return
|
|
52
|
+
fi
|
|
53
|
+
if command -v realpath >/dev/null 2>&1; then
|
|
54
|
+
realpath "$p" 2>/dev/null && return
|
|
55
|
+
fi
|
|
56
|
+
(cd "$p" 2>/dev/null && pwd) || echo "$p"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# JSON output helpers (standalone, for direct execution mode)
|
|
60
|
+
_clash_json_ok() {
|
|
61
|
+
echo "{\"ok\":true,\"result\":$1}"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_clash_json_err() {
|
|
65
|
+
echo "{\"ok\":false,\"error\":$1}" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Bridge: use aether-utils JSON helpers when sourced, standalone when direct
|
|
70
|
+
_cok() {
|
|
71
|
+
if type json_ok &>/dev/null; then
|
|
72
|
+
json_ok "$1"
|
|
73
|
+
else
|
|
74
|
+
_clash_json_ok "$1"
|
|
75
|
+
fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_cerr() {
|
|
79
|
+
if type json_err &>/dev/null; then
|
|
80
|
+
json_err "${2:-$E_UNKNOWN}" "$1"
|
|
81
|
+
else
|
|
82
|
+
_clash_json_err "\"$1\""
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Main clash detection logic
|
|
87
|
+
_clash_detect() {
|
|
88
|
+
local file=""
|
|
89
|
+
local worktree="${WORKTREE_DIR:-}"
|
|
90
|
+
|
|
91
|
+
while [[ $# -gt 0 ]]; do
|
|
92
|
+
case "$1" in
|
|
93
|
+
--file) file="${2:-}"; shift 2 ;;
|
|
94
|
+
--worktree) worktree="${2:-}"; shift 2 ;;
|
|
95
|
+
*) shift ;;
|
|
96
|
+
esac
|
|
97
|
+
done
|
|
98
|
+
|
|
99
|
+
if [[ -z "$file" ]]; then
|
|
100
|
+
_cerr "Usage: clash-detect --file <path> [--worktree <path>]" "$E_VALIDATION_FAILED"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# Allowlisted files never clash (branch-local state)
|
|
104
|
+
if _clash_is_allowlisted "$file"; then
|
|
105
|
+
_cok '{"conflict":false}'
|
|
106
|
+
return
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Verify we're in a git repo
|
|
110
|
+
if ! git -C "$AETHER_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
111
|
+
_cok '{"conflict":false}'
|
|
112
|
+
return
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Normalize the current worktree path for comparison
|
|
116
|
+
if [[ -n "$worktree" ]]; then
|
|
117
|
+
worktree="$(_clash_normalize_path "$worktree")" || worktree=""
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Enumerate all worktrees
|
|
121
|
+
local conflicting=()
|
|
122
|
+
while IFS= read -r line; do
|
|
123
|
+
local wt_path
|
|
124
|
+
wt_path=$(echo "$line" | awk '{print $1}')
|
|
125
|
+
[[ -z "$wt_path" ]] && continue
|
|
126
|
+
|
|
127
|
+
local abs_wt_path
|
|
128
|
+
abs_wt_path="$(_clash_normalize_path "$wt_path")" || continue
|
|
129
|
+
|
|
130
|
+
# Skip if this is the current worktree (self-check)
|
|
131
|
+
if [[ -n "$worktree" && "$abs_wt_path" == "$worktree" ]]; then
|
|
132
|
+
continue
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Check if this worktree has uncommitted changes to the target file
|
|
136
|
+
local file_status
|
|
137
|
+
file_status=$(git -C "$abs_wt_path" status --porcelain -- "$file" 2>/dev/null) || continue
|
|
138
|
+
|
|
139
|
+
if [[ -n "$file_status" ]]; then
|
|
140
|
+
local branch_name
|
|
141
|
+
branch_name=$(git -C "$abs_wt_path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
142
|
+
conflicting+=("\"$branch_name\"")
|
|
143
|
+
fi
|
|
144
|
+
done < <(git -C "$AETHER_ROOT" worktree list 2>/dev/null | tail -n +2)
|
|
145
|
+
|
|
146
|
+
if [[ ${#conflicting[@]} -eq 0 ]]; then
|
|
147
|
+
_cok '{"conflict":false}'
|
|
148
|
+
else
|
|
149
|
+
local conflict_list
|
|
150
|
+
conflict_list=$(printf '%s,' "${conflicting[@]}")
|
|
151
|
+
conflict_list="[${conflict_list%,}]"
|
|
152
|
+
_cok "{\"conflict\":true,\"conflicting_worktrees\":$conflict_list}"
|
|
153
|
+
fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# _clash_setup
|
|
157
|
+
# Install or uninstall the clash detection PreToolUse hook.
|
|
158
|
+
#
|
|
159
|
+
# Usage: clash-setup [--install] [--uninstall]
|
|
160
|
+
# Returns JSON: {ok:true, result:{hook_installed:true/false}}
|
|
161
|
+
#
|
|
162
|
+
# Environment:
|
|
163
|
+
# CLASH_SETTINGS_PATH - path to settings.json (default: .claude/settings.json)
|
|
164
|
+
_clash_setup() {
|
|
165
|
+
local action=""
|
|
166
|
+
local settings_path="${CLASH_SETTINGS_PATH:-$AETHER_ROOT/.claude/settings.json}"
|
|
167
|
+
|
|
168
|
+
while [[ $# -gt 0 ]]; do
|
|
169
|
+
case "$1" in
|
|
170
|
+
--install) action="install"; shift ;;
|
|
171
|
+
--uninstall) action="uninstall"; shift ;;
|
|
172
|
+
*) shift ;;
|
|
173
|
+
esac
|
|
174
|
+
done
|
|
175
|
+
|
|
176
|
+
if [[ -z "$action" ]]; then
|
|
177
|
+
_cerr "Usage: clash-setup [--install] [--uninstall]" "$E_VALIDATION_FAILED"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Ensure settings file exists
|
|
181
|
+
if [[ ! -f "$settings_path" ]]; then
|
|
182
|
+
echo '{}' > "$settings_path" 2>/dev/null || {
|
|
183
|
+
_cerr "Cannot create settings file at $settings_path" "$E_FILE_NOT_FOUND"
|
|
184
|
+
}
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
# Read current settings
|
|
188
|
+
local settings
|
|
189
|
+
settings=$(cat "$settings_path" 2>/dev/null) || settings='{}'
|
|
190
|
+
|
|
191
|
+
local hook_command='node .aether/utils/hooks/clash-pre-tool-use.js'
|
|
192
|
+
local hook_entry="{\"type\":\"command\",\"command\":\"$hook_command\",\"timeout\":5}"
|
|
193
|
+
|
|
194
|
+
if [[ "$action" == "install" ]]; then
|
|
195
|
+
# Check if hook already exists in PreToolUse
|
|
196
|
+
if echo "$settings" | jq -e '.hooks.PreToolUse[]?.hooks[]?.command' 2>/dev/null \
|
|
197
|
+
| grep -q "clash-pre-tool-use"; then
|
|
198
|
+
# Already installed
|
|
199
|
+
_cok '{"hook_installed":true}'
|
|
200
|
+
return
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Add the hook to PreToolUse array
|
|
204
|
+
settings=$(echo "$settings" | jq --arg entry "$hook_entry" '
|
|
205
|
+
if .hooks.PreToolUse then
|
|
206
|
+
.hooks.PreToolUse += [{"matcher": "Edit|Write", "hooks": [($entry | fromjson)]}]
|
|
207
|
+
else
|
|
208
|
+
.hooks.PreToolUse = [{"matcher": "Edit|Write", "hooks": [($entry | fromjson)]}]
|
|
209
|
+
end
|
|
210
|
+
' 2>/dev/null)
|
|
211
|
+
|
|
212
|
+
if [[ $? -ne 0 ]] || [[ -z "$settings" ]]; then
|
|
213
|
+
_cerr "Failed to update settings file" "$E_UNKNOWN"
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
echo "$settings" > "$settings_path"
|
|
217
|
+
|
|
218
|
+
_cok '{"hook_installed":true}'
|
|
219
|
+
|
|
220
|
+
elif [[ "$action" == "uninstall" ]]; then
|
|
221
|
+
# Remove the hook from PreToolUse
|
|
222
|
+
settings=$(echo "$settings" | jq '
|
|
223
|
+
if .hooks.PreToolUse then
|
|
224
|
+
.hooks.PreToolUse = [.hooks.PreToolUse[] |
|
|
225
|
+
select(.hooks[]?.command | . != "node .aether/utils/hooks/clash-pre-tool-use.js")
|
|
226
|
+
]
|
|
227
|
+
end
|
|
228
|
+
' 2>/dev/null)
|
|
229
|
+
|
|
230
|
+
echo "$settings" > "$settings_path"
|
|
231
|
+
|
|
232
|
+
_cok '{"hook_installed":false}'
|
|
233
|
+
fi
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
# Run if executed directly (not sourced)
|
|
237
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
238
|
+
_clash_detect "$@"
|
|
239
|
+
fi
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Clash Detection PreToolUse Hook
|
|
3
|
+
//
|
|
4
|
+
// Runs before Edit/Write tool calls in Claude Code.
|
|
5
|
+
// Checks if any other active git worktree has uncommitted changes to the
|
|
6
|
+
// same file, preventing agents from silently overwriting each other's work.
|
|
7
|
+
//
|
|
8
|
+
// Exit codes:
|
|
9
|
+
// 0 - Allow the operation
|
|
10
|
+
// 2 - Block the operation (conflict detected)
|
|
11
|
+
//
|
|
12
|
+
// Design principle: fail-open. If the hook errors for any reason,
|
|
13
|
+
// the operation is allowed. The goal is safety guidance, not blocking work.
|
|
14
|
+
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// Files that are branch-local state and never clash across worktrees
|
|
19
|
+
const ALLOWLIST = [
|
|
20
|
+
'.aether/data/',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Timeout for clash-detect subprocess (ms)
|
|
24
|
+
const DETECT_TIMEOUT = 5000;
|
|
25
|
+
|
|
26
|
+
let input = '';
|
|
27
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
28
|
+
process.stdin.setEncoding('utf8');
|
|
29
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
30
|
+
process.stdin.on('end', () => {
|
|
31
|
+
clearTimeout(stdinTimeout);
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(input);
|
|
34
|
+
const toolName = data.tool_name;
|
|
35
|
+
|
|
36
|
+
// Only check Edit and Write operations
|
|
37
|
+
if (toolName !== 'Edit' && toolName !== 'Write') {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extract file path
|
|
42
|
+
const filePath = data.tool_input?.file_path || '';
|
|
43
|
+
if (!filePath) {
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check allowlist (branch-local state files never clash)
|
|
48
|
+
if (ALLOWLIST.some(pattern => filePath.includes(pattern))) {
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Find the clash-detect script
|
|
53
|
+
// It lives at .aether/utils/clash-detect.sh relative to the repo root
|
|
54
|
+
const cwd = data.cwd || process.cwd();
|
|
55
|
+
const repoClashDetect = path.join(cwd, '.aether', 'utils', 'clash-detect.sh');
|
|
56
|
+
|
|
57
|
+
// Extract just the relative file path for clash-detect
|
|
58
|
+
const relPath = path.relative(cwd, filePath);
|
|
59
|
+
|
|
60
|
+
// Determine which clash-detect to use: repo-local or PATH
|
|
61
|
+
const fs = require('fs');
|
|
62
|
+
const clashDetect = fs.existsSync(repoClashDetect) ? repoClashDetect : 'clash-detect';
|
|
63
|
+
|
|
64
|
+
// Run clash-detect
|
|
65
|
+
try {
|
|
66
|
+
const cmd = clashDetect === 'clash-detect'
|
|
67
|
+
? `clash-detect --file "${relPath}"`
|
|
68
|
+
: `bash "${clashDetect}" --file "${relPath}"`;
|
|
69
|
+
const result = execSync(cmd, {
|
|
70
|
+
timeout: DETECT_TIMEOUT,
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
cwd: cwd,
|
|
73
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const parsed = JSON.parse(result.trim());
|
|
77
|
+
if (parsed.ok && parsed.result?.conflict) {
|
|
78
|
+
const worktrees = (parsed.result.conflicting_worktrees || []).join(', ');
|
|
79
|
+
const output = {
|
|
80
|
+
decision: 'block',
|
|
81
|
+
reason: `File "${relPath}" has uncommitted changes in worktree(s): ${worktrees}. ` +
|
|
82
|
+
'Coordinate with the other agent or wait for their changes to be committed.',
|
|
83
|
+
};
|
|
84
|
+
process.stderr.write(JSON.stringify(output) + '\n');
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// No conflict -- allow
|
|
89
|
+
process.exit(0);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
// Fail-open: if clash-detect fails, allow the operation
|
|
92
|
+
// This prevents the hook from blocking all work when something goes wrong
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// Fail-open on any parsing or unexpected error
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Merge Driver: npm lockfile auto-merge
|
|
3
|
+
#
|
|
4
|
+
# Resolves package-lock.json merge conflicts by keeping "ours" (the branch
|
|
5
|
+
# being merged into). Lockfiles are deterministic outputs of package.json,
|
|
6
|
+
# so the correct resolution is always to regenerate from the target branch's
|
|
7
|
+
# package.json, which effectively means keeping "ours".
|
|
8
|
+
#
|
|
9
|
+
# Usage: merge-driver-lockfile.sh %O %A %B
|
|
10
|
+
# %O = ancestor (base version)
|
|
11
|
+
# %A = ours (current branch, the version we want to keep)
|
|
12
|
+
# %B = theirs (incoming branch)
|
|
13
|
+
#
|
|
14
|
+
# Git merge driver contract:
|
|
15
|
+
# Exit 0 = conflict resolved (merge continues)
|
|
16
|
+
# Exit non-zero = conflict unresolved
|
|
17
|
+
#
|
|
18
|
+
# This driver is configured via:
|
|
19
|
+
# git config merge.lockfile.name "npm lockfile auto-merge"
|
|
20
|
+
# git config merge.lockfile.driver "bash .aether/utils/merge-driver-lockfile.sh %O %A %B"
|
|
21
|
+
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
ANCESTOR="${1:-}"
|
|
25
|
+
OURS="${2:-}"
|
|
26
|
+
THEIRS="${3:-}"
|
|
27
|
+
|
|
28
|
+
# Strategy: keep "ours" unchanged.
|
|
29
|
+
# The "ours" file already contains the correct content on disk.
|
|
30
|
+
# We do nothing -- git considers the file resolved when we exit 0.
|
|
31
|
+
#
|
|
32
|
+
# Log for debugging (to stderr so it does not pollute merge output)
|
|
33
|
+
echo "[aether] merge-driver: resolved package-lock.json conflict (kept ours)" >&2
|
|
34
|
+
|
|
35
|
+
exit 0
|