aether-colony 3.1.17 → 5.0.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/{runtime → .aether}/CONTEXT.md +1 -1
- package/{runtime → .aether}/aether-utils.sh +1772 -98
- package/.aether/docs/QUEEN-SYSTEM.md +211 -0
- package/.aether/docs/QUEEN.md +84 -0
- package/.aether/docs/README.md +68 -0
- package/.aether/docs/caste-system.md +48 -0
- package/{runtime → .aether/docs/disciplines}/DISCIPLINES.md +8 -8
- package/.aether/docs/error-codes.md +268 -0
- package/{runtime → .aether}/docs/known-issues.md +42 -26
- package/.aether/docs/queen-commands.md +97 -0
- package/.aether/exchange/colony-registry.xml +11 -0
- package/{runtime → .aether}/exchange/pheromone-xml.sh +2 -1
- package/.aether/exchange/pheromones.xml +87 -0
- package/.aether/exchange/queen-wisdom.xml +14 -0
- package/{runtime → .aether}/exchange/registry-xml.sh +7 -3
- package/{runtime → .aether}/exchange/wisdom-xml.sh +11 -4
- package/.aether/rules/aether-colony.md +134 -0
- package/.aether/schemas/example-prompt-builder.xml +234 -0
- package/.aether/templates/colony-state-reset.jq.template +22 -0
- package/.aether/templates/colony-state.template.json +35 -0
- package/.aether/templates/constraints.template.json +9 -0
- package/.aether/templates/crowned-anthill.template.md +36 -0
- package/.aether/templates/handoff-build-error.template.md +30 -0
- package/.aether/templates/handoff-build-success.template.md +39 -0
- package/.aether/templates/handoff.template.md +40 -0
- package/{runtime → .aether}/utils/atomic-write.sh +5 -5
- package/{runtime → .aether}/utils/chamber-compare.sh +23 -10
- package/{runtime → .aether}/utils/chamber-utils.sh +32 -20
- package/{runtime → .aether}/utils/error-handler.sh +13 -1
- package/{runtime → .aether}/utils/file-lock.sh +49 -13
- package/.aether/utils/semantic-cli.sh +413 -0
- package/{runtime → .aether}/utils/xml-compose.sh +7 -1
- package/.aether/utils/xml-convert.sh +273 -0
- package/.aether/utils/xml-query.sh +201 -0
- package/.aether/utils/xml-utils.sh +110 -0
- package/{runtime → .aether}/workers.md +14 -17
- package/.claude/agents/ant/aether-ambassador.md +264 -0
- package/.claude/agents/ant/aether-archaeologist.md +322 -0
- package/.claude/agents/ant/aether-auditor.md +266 -0
- package/.claude/agents/ant/aether-builder.md +187 -0
- package/.claude/agents/ant/aether-chaos.md +268 -0
- package/.claude/agents/ant/aether-chronicler.md +304 -0
- package/.claude/agents/ant/aether-gatekeeper.md +325 -0
- package/.claude/agents/ant/aether-includer.md +373 -0
- package/.claude/agents/ant/aether-keeper.md +271 -0
- package/.claude/agents/ant/aether-measurer.md +317 -0
- package/.claude/agents/ant/aether-probe.md +210 -0
- package/.claude/agents/ant/aether-queen.md +325 -0
- package/.claude/agents/ant/aether-route-setter.md +173 -0
- package/.claude/agents/ant/aether-sage.md +353 -0
- package/.claude/agents/ant/aether-scout.md +142 -0
- package/.claude/agents/ant/aether-surveyor-disciplines.md +416 -0
- package/.claude/agents/ant/aether-surveyor-nest.md +354 -0
- package/.claude/agents/ant/aether-surveyor-pathogens.md +288 -0
- package/.claude/agents/ant/aether-surveyor-provisions.md +359 -0
- package/.claude/agents/ant/aether-tracker.md +265 -0
- package/.claude/agents/ant/aether-watcher.md +244 -0
- package/.claude/agents/ant/aether-weaver.md +247 -0
- package/.claude/commands/ant/archaeology.md +16 -7
- package/.claude/commands/ant/build.md +415 -284
- package/.claude/commands/ant/chaos.md +19 -10
- package/.claude/commands/ant/colonize.md +58 -24
- package/.claude/commands/ant/continue.md +155 -145
- package/.claude/commands/ant/council.md +15 -5
- package/.claude/commands/ant/dream.md +16 -7
- package/.claude/commands/ant/entomb.md +274 -157
- package/.claude/commands/ant/feedback.md +33 -29
- package/.claude/commands/ant/flag.md +18 -10
- package/.claude/commands/ant/flags.md +14 -6
- package/.claude/commands/ant/focus.md +29 -21
- package/.claude/commands/ant/help.md +11 -1
- package/.claude/commands/ant/history.md +10 -0
- package/.claude/commands/ant/init.md +91 -65
- package/.claude/commands/ant/interpret.md +15 -4
- package/.claude/commands/ant/lay-eggs.md +55 -7
- package/.claude/commands/ant/maturity.md +11 -1
- package/.claude/commands/ant/migrate-state.md +14 -2
- package/.claude/commands/ant/oracle.md +23 -15
- package/.claude/commands/ant/organize.md +29 -20
- package/.claude/commands/ant/pause-colony.md +17 -7
- package/.claude/commands/ant/phase.md +17 -8
- package/.claude/commands/ant/plan.md +20 -9
- package/.claude/commands/ant/redirect.md +29 -32
- package/.claude/commands/ant/resume-colony.md +19 -9
- package/.claude/commands/ant/resume.md +272 -96
- package/.claude/commands/ant/seal.md +201 -191
- package/.claude/commands/ant/status.md +71 -32
- package/.claude/commands/ant/swarm.md +26 -44
- package/.claude/commands/ant/tunnels.md +279 -105
- package/.claude/commands/ant/update.md +81 -20
- package/.claude/commands/ant/verify-castes.md +14 -4
- package/.claude/commands/ant/watch.md +13 -12
- package/.opencode/agents/aether-ambassador.md +63 -20
- package/.opencode/agents/aether-archaeologist.md +29 -12
- package/.opencode/agents/aether-auditor.md +51 -18
- package/.opencode/agents/aether-builder.md +69 -19
- package/.opencode/agents/aether-chaos.md +29 -12
- package/.opencode/agents/aether-chronicler.md +60 -18
- package/.opencode/agents/aether-gatekeeper.md +27 -18
- package/.opencode/agents/aether-includer.md +27 -18
- package/.opencode/agents/aether-keeper.md +89 -18
- package/.opencode/agents/aether-measurer.md +27 -18
- package/.opencode/agents/aether-probe.md +60 -18
- package/.opencode/agents/aether-queen.md +172 -24
- package/.opencode/agents/aether-route-setter.md +57 -12
- package/.opencode/agents/aether-sage.md +26 -18
- package/.opencode/agents/aether-scout.md +27 -19
- package/.opencode/agents/aether-surveyor-disciplines.md +53 -1
- package/.opencode/agents/aether-surveyor-nest.md +53 -1
- package/.opencode/agents/aether-surveyor-pathogens.md +51 -1
- package/.opencode/agents/aether-surveyor-provisions.md +53 -1
- package/.opencode/agents/aether-tracker.md +64 -18
- package/.opencode/agents/aether-watcher.md +66 -19
- package/.opencode/agents/aether-weaver.md +61 -18
- package/.opencode/commands/ant/build.md +406 -192
- package/.opencode/commands/ant/continue.md +66 -76
- package/.opencode/commands/ant/entomb.md +106 -45
- package/.opencode/commands/ant/init.md +46 -48
- package/.opencode/commands/ant/organize.md +5 -5
- package/.opencode/commands/ant/resume.md +334 -0
- package/.opencode/commands/ant/seal.md +33 -24
- package/.opencode/commands/ant/status.md +11 -0
- package/.opencode/commands/ant/tunnels.md +149 -0
- package/.opencode/commands/ant/update.md +59 -16
- package/CHANGELOG.md +79 -0
- package/README.md +135 -353
- package/bin/cli.js +243 -122
- package/bin/generate-commands.sh +2 -2
- package/bin/lib/init.js +13 -3
- package/bin/lib/update-transaction.js +119 -117
- package/bin/sync-to-runtime.sh +5 -137
- package/bin/validate-package.sh +84 -0
- package/package.json +9 -6
- package/.opencode/agents/aether-architect.md +0 -66
- package/.opencode/agents/aether-guardian.md +0 -107
- package/.opencode/agents/workers.md +0 -1034
- package/runtime/QUEEN_ANT_ARCHITECTURE.md +0 -402
- package/runtime/data/signatures.json +0 -41
- package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +0 -1343
- package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +0 -2642
- package/runtime/docs/PHEROMONE-INJECTION.md +0 -240
- package/runtime/docs/PHEROMONE-INTEGRATION.md +0 -192
- package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +0 -426
- package/runtime/docs/README.md +0 -94
- package/runtime/docs/VISUAL-OUTPUT-SPEC.md +0 -219
- package/runtime/docs/biological-reference.md +0 -272
- package/runtime/docs/codebase-review.md +0 -399
- package/runtime/docs/command-sync.md +0 -164
- package/runtime/docs/constraints.md +0 -116
- package/runtime/docs/implementation-learnings.md +0 -89
- package/runtime/docs/namespace.md +0 -148
- package/runtime/docs/pathogen-schema-example.json +0 -36
- package/runtime/docs/pathogen-schema.md +0 -111
- package/runtime/docs/planning-discipline.md +0 -159
- package/runtime/docs/progressive-disclosure.md +0 -184
- package/runtime/lib/queen-utils.sh +0 -729
- package/runtime/planning.md +0 -159
- package/runtime/recover.sh +0 -136
- package/runtime/utils/xml-utils.sh +0 -2196
- package/runtime/workers-new-castes.md +0 -516
- /package/{runtime → .aether/docs/disciplines}/coding-standards.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/debugging.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/learning.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/tdd.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/verification-loop.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/verification.md +0 -0
- /package/{runtime → .aether}/docs/pheromones.md +0 -0
- /package/{runtime → .aether}/model-profiles.yaml +0 -0
- /package/{runtime → .aether}/schemas/aether-types.xsd +0 -0
- /package/{runtime → .aether}/schemas/colony-registry.xsd +0 -0
- /package/{runtime → .aether}/schemas/pheromone.xsd +0 -0
- /package/{runtime → .aether}/schemas/prompt.xsd +0 -0
- /package/{runtime → .aether}/schemas/queen-wisdom.xsd +0 -0
- /package/{runtime → .aether}/schemas/worker-priming.xsd +0 -0
- /package/{runtime → .aether}/templates/QUEEN.md.template +0 -0
- /package/{runtime → .aether}/utils/colorize-log.sh +0 -0
- /package/{runtime → .aether}/utils/queen-to-md.xsl +0 -0
- /package/{runtime → .aether}/utils/spawn-tree.sh +0 -0
- /package/{runtime → .aether}/utils/spawn-with-model.sh +0 -0
- /package/{runtime → .aether}/utils/state-loader.sh +0 -0
- /package/{runtime → .aether}/utils/swarm-display.sh +0 -0
- /package/{runtime → .aether}/utils/watch-spawn-tree.sh +0 -0
- /package/{runtime → .aether}/utils/xml-core.sh +0 -0
|
@@ -7,13 +7,26 @@ set -euo pipefail
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
8
|
CHAMBERS_DIR="${CHAMBERS_DIR:-.aether/chambers}"
|
|
9
9
|
|
|
10
|
+
# Source error-handler.sh for E_* constants and enhanced json_err
|
|
11
|
+
[[ -f "$SCRIPT_DIR/error-handler.sh" ]] && source "$SCRIPT_DIR/error-handler.sh"
|
|
12
|
+
|
|
13
|
+
# Fallback E_* constants (no-ops when error-handler.sh is already loaded)
|
|
14
|
+
: "${E_UNKNOWN:=E_UNKNOWN}"
|
|
15
|
+
: "${E_FILE_NOT_FOUND:=E_FILE_NOT_FOUND}"
|
|
16
|
+
: "${E_VALIDATION_FAILED:=E_VALIDATION_FAILED}"
|
|
17
|
+
|
|
10
18
|
# JSON output helpers
|
|
11
19
|
json_ok() { printf '{"ok":true,"result":%s}\n' "$1"; }
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
20
|
+
|
|
21
|
+
# Guard: yield to error-handler.sh's enhanced json_err when already loaded
|
|
22
|
+
if ! type json_err &>/dev/null; then
|
|
23
|
+
json_err() {
|
|
24
|
+
local code="${1:-E_UNKNOWN}"
|
|
25
|
+
local message="${2:-An unknown error occurred}"
|
|
26
|
+
printf '{"ok":false,"error":{"code":"%s","message":"%s"}}\n' "$code" "$message" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
}
|
|
29
|
+
fi
|
|
17
30
|
|
|
18
31
|
# Load chamber manifest
|
|
19
32
|
load_chamber() {
|
|
@@ -21,7 +34,7 @@ load_chamber() {
|
|
|
21
34
|
local manifest_file="$CHAMBERS_DIR/$chamber_name/manifest.json"
|
|
22
35
|
|
|
23
36
|
if [[ ! -f "$manifest_file" ]]; then
|
|
24
|
-
json_err "Chamber not found: $chamber_name"
|
|
37
|
+
json_err "$E_FILE_NOT_FOUND" "Chamber not found: $chamber_name. Try: check the chamber name with /ant:tunnels."
|
|
25
38
|
fi
|
|
26
39
|
|
|
27
40
|
cat "$manifest_file"
|
|
@@ -41,7 +54,7 @@ EOF
|
|
|
41
54
|
compare)
|
|
42
55
|
chamber_a="${1:-}"
|
|
43
56
|
chamber_b="${2:-}"
|
|
44
|
-
[[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "
|
|
57
|
+
[[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "$E_VALIDATION_FAILED" "Missing arguments. Try: compare <chamber_a> <chamber_b>."
|
|
45
58
|
|
|
46
59
|
# Load both manifests
|
|
47
60
|
manifest_a=$(load_chamber "$chamber_a")
|
|
@@ -93,7 +106,7 @@ EOF
|
|
|
93
106
|
diff)
|
|
94
107
|
chamber_a="${1:-}"
|
|
95
108
|
chamber_b="${2:-}"
|
|
96
|
-
[[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "
|
|
109
|
+
[[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "$E_VALIDATION_FAILED" "Missing arguments. Try: diff <chamber_a> <chamber_b>."
|
|
97
110
|
|
|
98
111
|
manifest_a=$(load_chamber "$chamber_a")
|
|
99
112
|
manifest_b=$(load_chamber "$chamber_b")
|
|
@@ -138,7 +151,7 @@ EOF
|
|
|
138
151
|
stats)
|
|
139
152
|
chamber_a="${1:-}"
|
|
140
153
|
chamber_b="${2:-}"
|
|
141
|
-
[[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "
|
|
154
|
+
[[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "$E_VALIDATION_FAILED" "Missing arguments. Try: stats <chamber_a> <chamber_b>."
|
|
142
155
|
|
|
143
156
|
manifest_a=$(load_chamber "$chamber_a")
|
|
144
157
|
manifest_b=$(load_chamber "$chamber_b")
|
|
@@ -175,6 +188,6 @@ EOF
|
|
|
175
188
|
;;
|
|
176
189
|
|
|
177
190
|
*)
|
|
178
|
-
json_err "Unknown command: $cmd.
|
|
191
|
+
json_err "$E_VALIDATION_FAILED" "Unknown command: $cmd. Try: compare, diff, or stats."
|
|
179
192
|
;;
|
|
180
193
|
esac
|
|
@@ -27,11 +27,23 @@ SCRIPT_DIR="${SCRIPT_DIR:-$__chamber_utils_dir}"
|
|
|
27
27
|
|
|
28
28
|
# --- JSON output helpers ---
|
|
29
29
|
json_ok() { printf '{"ok":true,"result":%s}\n' "$1"; }
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
30
|
+
|
|
31
|
+
# Guard: yield to error-handler.sh's enhanced json_err when already loaded
|
|
32
|
+
if ! type json_err &>/dev/null; then
|
|
33
|
+
json_err() {
|
|
34
|
+
local code="${1:-E_UNKNOWN}"
|
|
35
|
+
local message="${2:-An unknown error occurred}"
|
|
36
|
+
printf '{"ok":false,"error":{"code":"%s","message":"%s"}}\n' "$code" "$message" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
}
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Fallback E_* constants (no-ops when error-handler.sh is already loaded)
|
|
42
|
+
: "${E_UNKNOWN:=E_UNKNOWN}"
|
|
43
|
+
: "${E_VALIDATION_FAILED:=E_VALIDATION_FAILED}"
|
|
44
|
+
: "${E_FILE_NOT_FOUND:=E_FILE_NOT_FOUND}"
|
|
45
|
+
: "${E_BASH_ERROR:=E_BASH_ERROR}"
|
|
46
|
+
: "${E_JSON_INVALID:=E_JSON_INVALID}"
|
|
35
47
|
|
|
36
48
|
# --- Chamber Functions ---
|
|
37
49
|
|
|
@@ -93,20 +105,20 @@ chamber_create() {
|
|
|
93
105
|
local learnings_json="$9"
|
|
94
106
|
|
|
95
107
|
# Validate inputs
|
|
96
|
-
[[ -z "$chamber_dir" ]] && json_err "chamber_dir is required"
|
|
97
|
-
[[ -z "$state_file" ]] && json_err "state_file is required"
|
|
98
|
-
[[ ! -f "$state_file" ]] && json_err "State file not found: $state_file"
|
|
108
|
+
[[ -z "$chamber_dir" ]] && json_err "$E_VALIDATION_FAILED" "chamber_dir argument is required. Try: pass the chamber directory path."
|
|
109
|
+
[[ -z "$state_file" ]] && json_err "$E_VALIDATION_FAILED" "state_file argument is required. Try: pass the state file path."
|
|
110
|
+
[[ ! -f "$state_file" ]] && json_err "$E_FILE_NOT_FOUND" "State file not found: $state_file. Try: check the file path."
|
|
99
111
|
|
|
100
112
|
# Create chamber directory
|
|
101
|
-
mkdir -p "$chamber_dir" || json_err "
|
|
113
|
+
mkdir -p "$chamber_dir" || json_err "$E_BASH_ERROR" "Couldn't create chamber directory: $chamber_dir. Try: check disk space and permissions."
|
|
102
114
|
|
|
103
115
|
# Copy state file to chamber
|
|
104
116
|
local target_state="$chamber_dir/COLONY_STATE.json"
|
|
105
|
-
cp "$state_file" "$target_state" || json_err "
|
|
117
|
+
cp "$state_file" "$target_state" || json_err "$E_BASH_ERROR" "Couldn't copy the state file. Try: check disk space and permissions."
|
|
106
118
|
|
|
107
119
|
# Compute hash of the copied state file
|
|
108
120
|
local state_hash=$(chamber_compute_hash "$target_state")
|
|
109
|
-
[[ -z "$state_hash" ]] && json_err "
|
|
121
|
+
[[ -z "$state_hash" ]] && json_err "$E_BASH_ERROR" "Couldn't compute state file hash. Try: check that shasum is available."
|
|
110
122
|
|
|
111
123
|
# Generate timestamp
|
|
112
124
|
local entombed_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
@@ -132,14 +144,14 @@ EOF
|
|
|
132
144
|
|
|
133
145
|
# Write manifest atomically if atomic_write is available, otherwise direct
|
|
134
146
|
if type atomic_write &>/dev/null; then
|
|
135
|
-
atomic_write "$manifest_file" "$manifest_content" || json_err "
|
|
147
|
+
atomic_write "$manifest_file" "$manifest_content" || json_err "$E_BASH_ERROR" "Couldn't write chamber manifest. Try: check disk space."
|
|
136
148
|
else
|
|
137
|
-
echo "$manifest_content" > "$manifest_file" || json_err "
|
|
149
|
+
echo "$manifest_content" > "$manifest_file" || json_err "$E_BASH_ERROR" "Couldn't write chamber manifest. Try: check disk space."
|
|
138
150
|
fi
|
|
139
151
|
|
|
140
152
|
# Verify the manifest was written correctly
|
|
141
153
|
if [[ ! -f "$manifest_file" ]]; then
|
|
142
|
-
json_err "
|
|
154
|
+
json_err "$E_FILE_NOT_FOUND" "Chamber manifest wasn't created. Try: check disk space and permissions."
|
|
143
155
|
fi
|
|
144
156
|
|
|
145
157
|
# Return success with chamber info
|
|
@@ -168,23 +180,23 @@ chamber_verify() {
|
|
|
168
180
|
local chamber_dir="$1"
|
|
169
181
|
|
|
170
182
|
# Validate inputs
|
|
171
|
-
[[ -z "$chamber_dir" ]] && json_err "chamber_dir is required"
|
|
172
|
-
[[ ! -d "$chamber_dir" ]] && json_err "Chamber directory not found: $chamber_dir"
|
|
183
|
+
[[ -z "$chamber_dir" ]] && json_err "$E_VALIDATION_FAILED" "chamber_dir argument is required. Try: pass the chamber directory path."
|
|
184
|
+
[[ ! -d "$chamber_dir" ]] && json_err "$E_FILE_NOT_FOUND" "Chamber directory not found: $chamber_dir. Try: check the path."
|
|
173
185
|
|
|
174
186
|
local manifest_file="$chamber_dir/manifest.json"
|
|
175
187
|
local state_file="$chamber_dir/COLONY_STATE.json"
|
|
176
188
|
|
|
177
189
|
# Check required files exist
|
|
178
|
-
[[ ! -f "$manifest_file" ]] && json_err "Manifest not found in chamber"
|
|
179
|
-
[[ ! -f "$state_file" ]] && json_err "COLONY_STATE.json not found in chamber"
|
|
190
|
+
[[ ! -f "$manifest_file" ]] && json_err "$E_FILE_NOT_FOUND" "Manifest not found in chamber. Try: verify the chamber was created correctly."
|
|
191
|
+
[[ ! -f "$state_file" ]] && json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found in chamber. Try: re-entomb the colony."
|
|
180
192
|
|
|
181
193
|
# Read stored hash from manifest
|
|
182
194
|
local stored_hash=$(jq -r '.files["COLONY_STATE.json"] // empty' "$manifest_file" 2>/dev/null)
|
|
183
|
-
[[ -z "$stored_hash" ]] && json_err "No hash found in manifest
|
|
195
|
+
[[ -z "$stored_hash" ]] && json_err "$E_JSON_INVALID" "No hash found in manifest. Try: re-entomb the colony."
|
|
184
196
|
|
|
185
197
|
# Compute current hash
|
|
186
198
|
local current_hash=$(chamber_compute_hash "$state_file")
|
|
187
|
-
[[ -z "$current_hash" ]] && json_err "
|
|
199
|
+
[[ -z "$current_hash" ]] && json_err "$E_BASH_ERROR" "Couldn't compute state file hash. Try: check that shasum is available."
|
|
188
200
|
|
|
189
201
|
# Compare hashes
|
|
190
202
|
if [[ "$stored_hash" != "$current_hash" ]]; then
|
|
@@ -13,10 +13,13 @@ E_REPO_NOT_INITIALIZED="E_REPO_NOT_INITIALIZED"
|
|
|
13
13
|
E_FILE_NOT_FOUND="E_FILE_NOT_FOUND"
|
|
14
14
|
E_JSON_INVALID="E_JSON_INVALID"
|
|
15
15
|
E_LOCK_FAILED="E_LOCK_FAILED"
|
|
16
|
+
E_LOCK_STALE="E_LOCK_STALE"
|
|
16
17
|
E_GIT_ERROR="E_GIT_ERROR"
|
|
17
18
|
E_VALIDATION_FAILED="E_VALIDATION_FAILED"
|
|
18
19
|
E_FEATURE_UNAVAILABLE="E_FEATURE_UNAVAILABLE"
|
|
19
20
|
E_BASH_ERROR="E_BASH_ERROR"
|
|
21
|
+
E_DEPENDENCY_MISSING="E_DEPENDENCY_MISSING"
|
|
22
|
+
E_RESOURCE_NOT_FOUND="E_RESOURCE_NOT_FOUND"
|
|
20
23
|
|
|
21
24
|
# --- Recovery Suggestion Functions (internal, prefixed with _) ---
|
|
22
25
|
_recovery_hub_not_found() { echo '"Run: aether install"'; }
|
|
@@ -24,8 +27,11 @@ _recovery_repo_not_init() { echo '"Run /ant:init in this repo first"'; }
|
|
|
24
27
|
_recovery_file_not_found() { echo '"Check file path and permissions"'; }
|
|
25
28
|
_recovery_json_invalid() { echo '"Validate JSON syntax"'; }
|
|
26
29
|
_recovery_lock_failed() { echo '"Wait for other operations to complete"'; }
|
|
30
|
+
_recovery_lock_stale() { echo '"Remove the stale lock file manually or run: aether force-unlock"'; }
|
|
27
31
|
_recovery_git_error() { echo '"Check git status and resolve conflicts"'; }
|
|
28
32
|
_recovery_default() { echo 'null'; }
|
|
33
|
+
_recovery_dependency_missing() { echo '"Install the required dependency"'; }
|
|
34
|
+
_recovery_resource_not_found() { echo '"Check that the resource exists and try again"'; }
|
|
29
35
|
|
|
30
36
|
# Get recovery suggestion based on error code
|
|
31
37
|
_get_recovery() {
|
|
@@ -36,7 +42,10 @@ _get_recovery() {
|
|
|
36
42
|
"$E_FILE_NOT_FOUND") _recovery_file_not_found ;;
|
|
37
43
|
"$E_JSON_INVALID") _recovery_json_invalid ;;
|
|
38
44
|
"$E_LOCK_FAILED") _recovery_lock_failed ;;
|
|
45
|
+
"$E_LOCK_STALE") _recovery_lock_stale ;;
|
|
39
46
|
"$E_GIT_ERROR") _recovery_git_error ;;
|
|
47
|
+
"$E_DEPENDENCY_MISSING") _recovery_dependency_missing ;;
|
|
48
|
+
"$E_RESOURCE_NOT_FOUND") _recovery_resource_not_found ;;
|
|
40
49
|
*) _recovery_default ;;
|
|
41
50
|
esac
|
|
42
51
|
}
|
|
@@ -193,8 +202,11 @@ export -f json_err json_warn error_handler
|
|
|
193
202
|
export -f feature_enable feature_disable feature_enabled feature_log_degradation
|
|
194
203
|
export -f _get_recovery _recovery_hub_not_found _recovery_repo_not_init
|
|
195
204
|
export -f _recovery_file_not_found _recovery_json_invalid _recovery_lock_failed
|
|
205
|
+
export -f _recovery_lock_stale
|
|
196
206
|
export -f _recovery_git_error _recovery_default _feature_reason
|
|
207
|
+
export -f _recovery_dependency_missing _recovery_resource_not_found
|
|
197
208
|
export E_UNKNOWN E_HUB_NOT_FOUND E_REPO_NOT_INITIALIZED E_FILE_NOT_FOUND
|
|
198
|
-
export E_JSON_INVALID E_LOCK_FAILED E_GIT_ERROR E_VALIDATION_FAILED
|
|
209
|
+
export E_JSON_INVALID E_LOCK_FAILED E_LOCK_STALE E_GIT_ERROR E_VALIDATION_FAILED
|
|
199
210
|
export E_FEATURE_UNAVAILABLE E_BASH_ERROR
|
|
211
|
+
export E_DEPENDENCY_MISSING E_RESOURCE_NOT_FOUND
|
|
200
212
|
export _FEATURES_DISABLED
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# source .aether/utils/file-lock.sh
|
|
7
7
|
# acquire_lock /path/to/file.lock
|
|
8
8
|
# # ... critical section ...
|
|
9
|
-
# release_lock
|
|
9
|
+
# release_lock
|
|
10
10
|
|
|
11
11
|
# Aether root detection - use git root if available, otherwise use current directory
|
|
12
12
|
if git rev-parse --show-toplevel >/dev/null 2>&1; then
|
|
@@ -20,13 +20,16 @@ LOCK_TIMEOUT=300 # 5 minutes max lock time
|
|
|
20
20
|
LOCK_RETRY_INTERVAL=0.5 # Wait 500ms between retries
|
|
21
21
|
LOCK_MAX_RETRIES=100 # Total 50 seconds max wait
|
|
22
22
|
|
|
23
|
+
# Fallback constant — ensures E_LOCK_STALE is defined whether or not error-handler.sh was loaded
|
|
24
|
+
: "${E_LOCK_STALE:=E_LOCK_STALE}"
|
|
25
|
+
|
|
23
26
|
# Create lock directory if it doesn't exist
|
|
24
27
|
mkdir -p "$LOCK_DIR"
|
|
25
28
|
|
|
26
|
-
# Acquire a file lock using
|
|
29
|
+
# Acquire a file lock using noclobber
|
|
27
30
|
# Arguments: file_path (the resource to lock)
|
|
28
31
|
# Returns: 0 on success, 1 on failure
|
|
29
|
-
# Globals: LOCK_ACQUIRED (set to true when lock acquired)
|
|
32
|
+
# Globals: LOCK_ACQUIRED (set to true when lock acquired), CURRENT_LOCK (set to lock file path)
|
|
30
33
|
acquire_lock() {
|
|
31
34
|
local file_path="$1"
|
|
32
35
|
local lock_file="${LOCK_DIR}/$(basename "$file_path").lock"
|
|
@@ -34,12 +37,45 @@ acquire_lock() {
|
|
|
34
37
|
|
|
35
38
|
# Check if lock file exists and is stale
|
|
36
39
|
if [ -f "$lock_file" ]; then
|
|
37
|
-
local lock_pid
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
local lock_pid
|
|
41
|
+
lock_pid=$(cat "$lock_pid_file" 2>/dev/null || echo "")
|
|
42
|
+
local is_stale=false
|
|
43
|
+
|
|
44
|
+
# Check age FIRST (before PID check) to handle PID reuse race
|
|
45
|
+
local lock_mtime=0
|
|
46
|
+
# Platform-portable mtime: macOS uses stat -f %m, Linux uses stat -c %Y
|
|
47
|
+
if stat -f %m "$lock_file" >/dev/null 2>&1; then
|
|
48
|
+
lock_mtime=$(stat -f %m "$lock_file" 2>/dev/null || echo 0)
|
|
49
|
+
else
|
|
50
|
+
lock_mtime=$(stat -c %Y "$lock_file" 2>/dev/null || echo 0)
|
|
51
|
+
fi
|
|
52
|
+
local lock_age=$(( $(date +%s) - lock_mtime ))
|
|
53
|
+
|
|
54
|
+
if [[ $lock_age -gt $LOCK_TIMEOUT ]]; then
|
|
55
|
+
is_stale=true
|
|
56
|
+
elif [[ -n "$lock_pid" ]] && ! kill -0 "$lock_pid" 2>/dev/null; then
|
|
57
|
+
is_stale=true
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if [[ "$is_stale" == "true" ]]; then
|
|
61
|
+
# TTY-gated user prompt — never auto-remove stale locks (locked user decision)
|
|
62
|
+
if [[ -t 2 ]]; then
|
|
63
|
+
echo "" >&2
|
|
64
|
+
echo "Warning: stale lock detected (PID ${lock_pid:-unknown} not running, age ${lock_age}s)" >&2
|
|
65
|
+
echo "Lock file: $lock_file" >&2
|
|
66
|
+
printf "Remove stale lock and continue? [y/N] " >&2
|
|
67
|
+
local response
|
|
68
|
+
read -r response < /dev/tty
|
|
69
|
+
if [[ "$response" =~ ^[Yy]$ ]]; then
|
|
70
|
+
rm -f "$lock_file" "$lock_pid_file"
|
|
71
|
+
else
|
|
72
|
+
echo "Lock removal declined. Remove manually: rm $lock_file" >&2
|
|
73
|
+
return 1
|
|
74
|
+
fi
|
|
75
|
+
else
|
|
76
|
+
# Non-interactive: fail with structured JSON error — do NOT auto-remove
|
|
77
|
+
printf '{"ok":false,"error":{"code":"%s","message":"Stale lock found. Remove manually: %s"}}\n' "$E_LOCK_STALE" "$lock_file" >&2
|
|
78
|
+
return 1
|
|
43
79
|
fi
|
|
44
80
|
fi
|
|
45
81
|
fi
|
|
@@ -61,12 +97,12 @@ acquire_lock() {
|
|
|
61
97
|
fi
|
|
62
98
|
done
|
|
63
99
|
|
|
64
|
-
echo "Failed to acquire lock for $file_path after $LOCK_MAX_RETRIES attempts"
|
|
100
|
+
echo "Failed to acquire lock for $file_path after $LOCK_MAX_RETRIES attempts" >&2
|
|
65
101
|
return 1
|
|
66
102
|
}
|
|
67
103
|
|
|
68
104
|
# Release a file lock
|
|
69
|
-
# Arguments: None (uses CURRENT_LOCK
|
|
105
|
+
# Arguments: None (uses CURRENT_LOCK global set by acquire_lock)
|
|
70
106
|
release_lock() {
|
|
71
107
|
if [ "$LOCK_ACQUIRED" = "true" ] && [ -n "$CURRENT_LOCK" ]; then
|
|
72
108
|
rm -f "$CURRENT_LOCK" "${CURRENT_LOCK}.pid"
|
|
@@ -84,8 +120,8 @@ cleanup_locks() {
|
|
|
84
120
|
fi
|
|
85
121
|
}
|
|
86
122
|
|
|
87
|
-
# Register cleanup on exit
|
|
88
|
-
trap cleanup_locks EXIT TERM INT
|
|
123
|
+
# Register cleanup on exit — includes HUP for SSH disconnect safety
|
|
124
|
+
trap cleanup_locks EXIT TERM INT HUP
|
|
89
125
|
|
|
90
126
|
# Check if a file is currently locked
|
|
91
127
|
is_locked() {
|