aether-colony 1.1.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/.aether/CONTEXT.md +160 -0
- package/.aether/QUEEN.md +84 -0
- package/.aether/aether-utils.sh +7749 -0
- package/.aether/docs/QUEEN-SYSTEM.md +211 -0
- package/.aether/docs/README.md +68 -0
- package/.aether/docs/caste-system.md +48 -0
- package/.aether/docs/disciplines/DISCIPLINES.md +93 -0
- package/.aether/docs/disciplines/coding-standards.md +197 -0
- package/.aether/docs/disciplines/debugging.md +207 -0
- package/.aether/docs/disciplines/learning.md +254 -0
- package/.aether/docs/disciplines/tdd.md +257 -0
- package/.aether/docs/disciplines/verification-loop.md +167 -0
- package/.aether/docs/disciplines/verification.md +116 -0
- package/.aether/docs/error-codes.md +268 -0
- package/.aether/docs/known-issues.md +233 -0
- package/.aether/docs/pheromones.md +205 -0
- package/.aether/docs/queen-commands.md +97 -0
- package/.aether/exchange/colony-registry.xml +11 -0
- package/.aether/exchange/pheromone-xml.sh +575 -0
- package/.aether/exchange/pheromones.xml +87 -0
- package/.aether/exchange/queen-wisdom.xml +14 -0
- package/.aether/exchange/registry-xml.sh +273 -0
- package/.aether/exchange/wisdom-xml.sh +319 -0
- package/.aether/midden/approach-changes.md +5 -0
- package/.aether/midden/build-failures.md +5 -0
- package/.aether/midden/test-failures.md +5 -0
- package/.aether/model-profiles.yaml +100 -0
- package/.aether/rules/aether-colony.md +134 -0
- package/.aether/schemas/aether-types.xsd +255 -0
- package/.aether/schemas/colony-registry.xsd +309 -0
- package/.aether/schemas/example-prompt-builder.xml +234 -0
- package/.aether/schemas/pheromone.xsd +163 -0
- package/.aether/schemas/prompt.xsd +416 -0
- package/.aether/schemas/queen-wisdom.xsd +325 -0
- package/.aether/schemas/worker-priming.xsd +276 -0
- package/.aether/templates/QUEEN.md.template +79 -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/.aether/templates/learning-observations.template.json +6 -0
- package/.aether/templates/midden.template.json +7 -0
- package/.aether/templates/pheromones.template.json +6 -0
- package/.aether/templates/session.template.json +9 -0
- package/.aether/utils/atomic-write.sh +219 -0
- package/.aether/utils/chamber-compare.sh +193 -0
- package/.aether/utils/chamber-utils.sh +297 -0
- package/.aether/utils/colorize-log.sh +132 -0
- package/.aether/utils/error-handler.sh +212 -0
- package/.aether/utils/file-lock.sh +158 -0
- package/.aether/utils/queen-to-md.xsl +395 -0
- package/.aether/utils/semantic-cli.sh +413 -0
- package/.aether/utils/spawn-tree.sh +428 -0
- package/.aether/utils/spawn-with-model.sh +56 -0
- package/.aether/utils/state-loader.sh +215 -0
- package/.aether/utils/swarm-display.sh +268 -0
- package/.aether/utils/watch-spawn-tree.sh +253 -0
- package/.aether/utils/xml-compose.sh +253 -0
- package/.aether/utils/xml-convert.sh +273 -0
- package/.aether/utils/xml-core.sh +186 -0
- package/.aether/utils/xml-query.sh +201 -0
- package/.aether/utils/xml-utils.sh +110 -0
- package/.aether/workers.md +765 -0
- 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 +341 -0
- package/.claude/commands/ant/build.md +1160 -0
- package/.claude/commands/ant/chaos.md +349 -0
- package/.claude/commands/ant/colonize.md +270 -0
- package/.claude/commands/ant/continue.md +1070 -0
- package/.claude/commands/ant/council.md +309 -0
- package/.claude/commands/ant/dream.md +265 -0
- package/.claude/commands/ant/entomb.md +487 -0
- package/.claude/commands/ant/feedback.md +78 -0
- package/.claude/commands/ant/flag.md +139 -0
- package/.claude/commands/ant/flags.md +155 -0
- package/.claude/commands/ant/focus.md +58 -0
- package/.claude/commands/ant/help.md +122 -0
- package/.claude/commands/ant/history.md +137 -0
- package/.claude/commands/ant/init.md +409 -0
- package/.claude/commands/ant/interpret.md +267 -0
- package/.claude/commands/ant/lay-eggs.md +201 -0
- package/.claude/commands/ant/maturity.md +102 -0
- package/.claude/commands/ant/memory-details.md +77 -0
- package/.claude/commands/ant/migrate-state.md +165 -0
- package/.claude/commands/ant/oracle.md +387 -0
- package/.claude/commands/ant/organize.md +227 -0
- package/.claude/commands/ant/pause-colony.md +247 -0
- package/.claude/commands/ant/phase.md +126 -0
- package/.claude/commands/ant/plan.md +544 -0
- package/.claude/commands/ant/redirect.md +58 -0
- package/.claude/commands/ant/resume-colony.md +182 -0
- package/.claude/commands/ant/resume.md +363 -0
- package/.claude/commands/ant/seal.md +306 -0
- package/.claude/commands/ant/status.md +272 -0
- package/.claude/commands/ant/swarm.md +361 -0
- package/.claude/commands/ant/tunnels.md +425 -0
- package/.claude/commands/ant/update.md +209 -0
- package/.claude/commands/ant/verify-castes.md +95 -0
- package/.claude/commands/ant/watch.md +238 -0
- package/.opencode/agents/aether-ambassador.md +140 -0
- package/.opencode/agents/aether-archaeologist.md +108 -0
- package/.opencode/agents/aether-auditor.md +144 -0
- package/.opencode/agents/aether-builder.md +184 -0
- package/.opencode/agents/aether-chaos.md +115 -0
- package/.opencode/agents/aether-chronicler.md +122 -0
- package/.opencode/agents/aether-gatekeeper.md +116 -0
- package/.opencode/agents/aether-includer.md +117 -0
- package/.opencode/agents/aether-keeper.md +177 -0
- package/.opencode/agents/aether-measurer.md +128 -0
- package/.opencode/agents/aether-probe.md +133 -0
- package/.opencode/agents/aether-queen.md +286 -0
- package/.opencode/agents/aether-route-setter.md +130 -0
- package/.opencode/agents/aether-sage.md +106 -0
- package/.opencode/agents/aether-scout.md +101 -0
- package/.opencode/agents/aether-surveyor-disciplines.md +386 -0
- package/.opencode/agents/aether-surveyor-nest.md +324 -0
- package/.opencode/agents/aether-surveyor-pathogens.md +259 -0
- package/.opencode/agents/aether-surveyor-provisions.md +329 -0
- package/.opencode/agents/aether-tracker.md +137 -0
- package/.opencode/agents/aether-watcher.md +174 -0
- package/.opencode/agents/aether-weaver.md +130 -0
- package/.opencode/commands/ant/archaeology.md +338 -0
- package/.opencode/commands/ant/build.md +1200 -0
- package/.opencode/commands/ant/chaos.md +346 -0
- package/.opencode/commands/ant/colonize.md +202 -0
- package/.opencode/commands/ant/continue.md +938 -0
- package/.opencode/commands/ant/council.md +305 -0
- package/.opencode/commands/ant/dream.md +262 -0
- package/.opencode/commands/ant/entomb.md +367 -0
- package/.opencode/commands/ant/feedback.md +80 -0
- package/.opencode/commands/ant/flag.md +137 -0
- package/.opencode/commands/ant/flags.md +153 -0
- package/.opencode/commands/ant/focus.md +56 -0
- package/.opencode/commands/ant/help.md +124 -0
- package/.opencode/commands/ant/history.md +127 -0
- package/.opencode/commands/ant/init.md +337 -0
- package/.opencode/commands/ant/interpret.md +256 -0
- package/.opencode/commands/ant/lay-eggs.md +141 -0
- package/.opencode/commands/ant/maturity.md +92 -0
- package/.opencode/commands/ant/memory-details.md +77 -0
- package/.opencode/commands/ant/migrate-state.md +153 -0
- package/.opencode/commands/ant/oracle.md +338 -0
- package/.opencode/commands/ant/organize.md +224 -0
- package/.opencode/commands/ant/pause-colony.md +220 -0
- package/.opencode/commands/ant/phase.md +123 -0
- package/.opencode/commands/ant/plan.md +531 -0
- package/.opencode/commands/ant/redirect.md +67 -0
- package/.opencode/commands/ant/resume-colony.md +178 -0
- package/.opencode/commands/ant/resume.md +363 -0
- package/.opencode/commands/ant/seal.md +247 -0
- package/.opencode/commands/ant/status.md +272 -0
- package/.opencode/commands/ant/swarm.md +357 -0
- package/.opencode/commands/ant/tunnels.md +406 -0
- package/.opencode/commands/ant/update.md +191 -0
- package/.opencode/commands/ant/verify-castes.md +85 -0
- package/.opencode/commands/ant/watch.md +220 -0
- package/.opencode/opencode.json +3 -0
- package/CHANGELOG.md +325 -0
- package/DISCLAIMER.md +74 -0
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/bin/cli.js +2436 -0
- package/bin/generate-commands.sh +291 -0
- package/bin/lib/caste-colors.js +57 -0
- package/bin/lib/colors.js +76 -0
- package/bin/lib/errors.js +255 -0
- package/bin/lib/event-types.js +190 -0
- package/bin/lib/file-lock.js +695 -0
- package/bin/lib/init.js +454 -0
- package/bin/lib/logger.js +242 -0
- package/bin/lib/model-profiles.js +445 -0
- package/bin/lib/model-verify.js +288 -0
- package/bin/lib/nestmate-loader.js +130 -0
- package/bin/lib/proxy-health.js +253 -0
- package/bin/lib/spawn-logger.js +266 -0
- package/bin/lib/state-guard.js +602 -0
- package/bin/lib/state-sync.js +516 -0
- package/bin/lib/telemetry.js +441 -0
- package/bin/lib/update-transaction.js +1454 -0
- package/bin/npx-install.js +178 -0
- package/bin/sync-to-runtime.sh +6 -0
- package/bin/validate-package.sh +88 -0
- package/package.json +70 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Aether Chamber Utilities
|
|
3
|
+
# Manages entombed colonies — directory management, manifest generation, integrity verification
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# source .aether/utils/chamber-utils.sh
|
|
7
|
+
# chamber_create <chamber_dir> <state_file> <goal> <phases_completed> <total_phases> <milestone> <version> <decisions_json> <learnings_json>
|
|
8
|
+
# chamber_verify <chamber_dir>
|
|
9
|
+
# chamber_list <chambers_root>
|
|
10
|
+
# chamber_sanitize_goal <goal>
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
# Initialize lock state before sourcing (file-lock.sh trap needs these)
|
|
15
|
+
LOCK_ACQUIRED=${LOCK_ACQUIRED:-false}
|
|
16
|
+
CURRENT_LOCK=${CURRENT_LOCK:-""}
|
|
17
|
+
|
|
18
|
+
# Get script directory for sourcing (preserve parent SCRIPT_DIR if set)
|
|
19
|
+
__chamber_utils_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
AETHER_ROOT="$(cd "$__chamber_utils_dir/../.." && pwd 2>/dev/null || echo "$__chamber_utils_dir/../..")"
|
|
21
|
+
|
|
22
|
+
# Use parent SCRIPT_DIR if available, otherwise use local
|
|
23
|
+
SCRIPT_DIR="${SCRIPT_DIR:-$__chamber_utils_dir}"
|
|
24
|
+
|
|
25
|
+
# Source atomic-write for safe file operations
|
|
26
|
+
[[ -f "$SCRIPT_DIR/atomic-write.sh" ]] && source "$SCRIPT_DIR/atomic-write.sh"
|
|
27
|
+
|
|
28
|
+
# --- JSON output helpers ---
|
|
29
|
+
json_ok() { printf '{"ok":true,"result":%s}\n' "$1"; }
|
|
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}"
|
|
47
|
+
|
|
48
|
+
# --- Chamber Functions ---
|
|
49
|
+
|
|
50
|
+
# Sanitize goal string for use in directory names
|
|
51
|
+
# Converts to lowercase, replaces spaces/special chars with hyphens, removes non-alphanumeric
|
|
52
|
+
chamber_sanitize_goal() {
|
|
53
|
+
local goal="$1"
|
|
54
|
+
# Convert to lowercase, replace spaces and special chars with hyphens
|
|
55
|
+
local sanitized=$(echo "$goal" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-')
|
|
56
|
+
# Remove leading/trailing hyphens
|
|
57
|
+
sanitized=$(echo "$sanitized" | sed 's/^-//;s/-$//')
|
|
58
|
+
# Limit length to avoid overly long directory names
|
|
59
|
+
if [[ ${#sanitized} -gt 50 ]]; then
|
|
60
|
+
sanitized="${sanitized:0:50}"
|
|
61
|
+
fi
|
|
62
|
+
echo "$sanitized"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Compute SHA256 hash of a file
|
|
66
|
+
# Returns hash string or empty on error
|
|
67
|
+
chamber_compute_hash() {
|
|
68
|
+
local file_path="$1"
|
|
69
|
+
if [[ ! -f "$file_path" ]]; then
|
|
70
|
+
echo ""
|
|
71
|
+
return 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# Try sha256sum first (Linux), then shasum -a 256 (macOS)
|
|
75
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
76
|
+
sha256sum "$file_path" | cut -d' ' -f1
|
|
77
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
78
|
+
shasum -a 256 "$file_path" | cut -d' ' -f1
|
|
79
|
+
else
|
|
80
|
+
echo ""
|
|
81
|
+
return 1
|
|
82
|
+
fi
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Create a new chamber (entomb a colony)
|
|
86
|
+
# Arguments:
|
|
87
|
+
# chamber_dir: Directory to create for this chamber
|
|
88
|
+
# state_file: Path to COLONY_STATE.json to archive
|
|
89
|
+
# goal: Colony goal string
|
|
90
|
+
# phases_completed: Number of completed phases
|
|
91
|
+
# total_phases: Total number of phases
|
|
92
|
+
# milestone: Milestone name
|
|
93
|
+
# version: Version string
|
|
94
|
+
# decisions_json: JSON array of decisions
|
|
95
|
+
# learnings_json: JSON array of learnings
|
|
96
|
+
chamber_create() {
|
|
97
|
+
local chamber_dir="$1"
|
|
98
|
+
local state_file="$2"
|
|
99
|
+
local goal="$3"
|
|
100
|
+
local phases_completed="$4"
|
|
101
|
+
local total_phases="$5"
|
|
102
|
+
local milestone="$6"
|
|
103
|
+
local version="$7"
|
|
104
|
+
local decisions_json="$8"
|
|
105
|
+
local learnings_json="$9"
|
|
106
|
+
|
|
107
|
+
# Validate inputs
|
|
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."
|
|
111
|
+
|
|
112
|
+
# Create chamber directory
|
|
113
|
+
mkdir -p "$chamber_dir" || json_err "$E_BASH_ERROR" "Couldn't create chamber directory: $chamber_dir. Try: check disk space and permissions."
|
|
114
|
+
|
|
115
|
+
# Copy state file to chamber
|
|
116
|
+
local target_state="$chamber_dir/COLONY_STATE.json"
|
|
117
|
+
cp "$state_file" "$target_state" || json_err "$E_BASH_ERROR" "Couldn't copy the state file. Try: check disk space and permissions."
|
|
118
|
+
|
|
119
|
+
# Compute hash of the copied state file
|
|
120
|
+
local state_hash=$(chamber_compute_hash "$target_state")
|
|
121
|
+
[[ -z "$state_hash" ]] && json_err "$E_BASH_ERROR" "Couldn't compute state file hash. Try: check that shasum is available."
|
|
122
|
+
|
|
123
|
+
# Generate timestamp
|
|
124
|
+
local entombed_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
125
|
+
|
|
126
|
+
# Create manifest.json
|
|
127
|
+
local manifest_file="$chamber_dir/manifest.json"
|
|
128
|
+
local manifest_content=$(cat <<EOF
|
|
129
|
+
{
|
|
130
|
+
"entombed_at": "$entombed_at",
|
|
131
|
+
"goal": $(echo "$goal" | jq -Rs '.[:-1]'),
|
|
132
|
+
"phases_completed": $phases_completed,
|
|
133
|
+
"total_phases": $total_phases,
|
|
134
|
+
"milestone": $(echo "$milestone" | jq -Rs '.[:-1]'),
|
|
135
|
+
"version": $(echo "$version" | jq -Rs '.[:-1]'),
|
|
136
|
+
"decisions": $decisions_json,
|
|
137
|
+
"learnings": $learnings_json,
|
|
138
|
+
"files": {
|
|
139
|
+
"COLONY_STATE.json": "$state_hash"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
EOF
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Write manifest atomically if atomic_write is available, otherwise direct
|
|
146
|
+
if type atomic_write &>/dev/null; then
|
|
147
|
+
atomic_write "$manifest_file" "$manifest_content" || json_err "$E_BASH_ERROR" "Couldn't write chamber manifest. Try: check disk space."
|
|
148
|
+
else
|
|
149
|
+
echo "$manifest_content" > "$manifest_file" || json_err "$E_BASH_ERROR" "Couldn't write chamber manifest. Try: check disk space."
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# Verify the manifest was written correctly
|
|
153
|
+
if [[ ! -f "$manifest_file" ]]; then
|
|
154
|
+
json_err "$E_FILE_NOT_FOUND" "Chamber manifest wasn't created. Try: check disk space and permissions."
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
# Return success with chamber info
|
|
158
|
+
local result=$(cat <<EOF
|
|
159
|
+
{
|
|
160
|
+
"chamber_dir": "$chamber_dir",
|
|
161
|
+
"manifest": {
|
|
162
|
+
"entombed_at": "$entombed_at",
|
|
163
|
+
"goal": $(echo "$goal" | jq -Rs '.[:-1]'),
|
|
164
|
+
"phases_completed": $phases_completed,
|
|
165
|
+
"total_phases": $total_phases,
|
|
166
|
+
"milestone": $(echo "$milestone" | jq -Rs '.[:-1]'),
|
|
167
|
+
"version": $(echo "$version" | jq -Rs '.[:-1]')
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
EOF
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
json_ok "$result"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Verify chamber integrity
|
|
177
|
+
# Arguments:
|
|
178
|
+
# chamber_dir: Directory containing the chamber
|
|
179
|
+
chamber_verify() {
|
|
180
|
+
local chamber_dir="$1"
|
|
181
|
+
|
|
182
|
+
# Validate inputs
|
|
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."
|
|
185
|
+
|
|
186
|
+
local manifest_file="$chamber_dir/manifest.json"
|
|
187
|
+
local state_file="$chamber_dir/COLONY_STATE.json"
|
|
188
|
+
|
|
189
|
+
# Check required files exist
|
|
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."
|
|
192
|
+
|
|
193
|
+
# Read stored hash from manifest
|
|
194
|
+
local stored_hash=$(jq -r '.files["COLONY_STATE.json"] // empty' "$manifest_file" 2>/dev/null)
|
|
195
|
+
[[ -z "$stored_hash" ]] && json_err "$E_JSON_INVALID" "No hash found in manifest. Try: re-entomb the colony."
|
|
196
|
+
|
|
197
|
+
# Compute current hash
|
|
198
|
+
local current_hash=$(chamber_compute_hash "$state_file")
|
|
199
|
+
[[ -z "$current_hash" ]] && json_err "$E_BASH_ERROR" "Couldn't compute state file hash. Try: check that shasum is available."
|
|
200
|
+
|
|
201
|
+
# Compare hashes
|
|
202
|
+
if [[ "$stored_hash" != "$current_hash" ]]; then
|
|
203
|
+
local result=$(cat <<EOF
|
|
204
|
+
{
|
|
205
|
+
"verified": false,
|
|
206
|
+
"chamber_dir": "$chamber_dir",
|
|
207
|
+
"error": "hash mismatch",
|
|
208
|
+
"stored_hash": "$stored_hash",
|
|
209
|
+
"current_hash": "$current_hash"
|
|
210
|
+
}
|
|
211
|
+
EOF
|
|
212
|
+
)
|
|
213
|
+
json_ok "$result"
|
|
214
|
+
return 0
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# Verification passed
|
|
218
|
+
local result=$(cat <<EOF
|
|
219
|
+
{
|
|
220
|
+
"verified": true,
|
|
221
|
+
"chamber_dir": "$chamber_dir",
|
|
222
|
+
"hash": "$current_hash"
|
|
223
|
+
}
|
|
224
|
+
EOF
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
json_ok "$result"
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# List all chambers
|
|
231
|
+
# Arguments:
|
|
232
|
+
# chambers_root: Root directory containing chambers (default: .aether/chambers/)
|
|
233
|
+
chamber_list() {
|
|
234
|
+
local chambers_root="${1:-$AETHER_ROOT/.aether/chambers}"
|
|
235
|
+
|
|
236
|
+
# Default to current directory's chambers if AETHER_ROOT not set
|
|
237
|
+
if [[ -z "$chambers_root" || "$chambers_root" == "/.aether/chambers" ]]; then
|
|
238
|
+
chambers_root="$(pwd)/.aether/chambers"
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# Check if chambers directory exists
|
|
242
|
+
if [[ ! -d "$chambers_root" ]]; then
|
|
243
|
+
json_ok "[]"
|
|
244
|
+
return 0
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
# Build array of chamber summaries
|
|
248
|
+
local chambers="["
|
|
249
|
+
local first=true
|
|
250
|
+
|
|
251
|
+
# Find all directories in chambers_root
|
|
252
|
+
while IFS= read -r -d '' chamber_dir; do
|
|
253
|
+
local chamber_name=$(basename "$chamber_dir")
|
|
254
|
+
local manifest_file="$chamber_dir/manifest.json"
|
|
255
|
+
|
|
256
|
+
# Skip if no manifest
|
|
257
|
+
[[ ! -f "$manifest_file" ]] && continue
|
|
258
|
+
|
|
259
|
+
# Read manifest fields
|
|
260
|
+
local goal=$(jq -r '.goal // "unknown"' "$manifest_file" 2>/dev/null)
|
|
261
|
+
local milestone=$(jq -r '.milestone // "unknown"' "$manifest_file" 2>/dev/null)
|
|
262
|
+
local phases_completed=$(jq -r '.phases_completed // 0' "$manifest_file" 2>/dev/null)
|
|
263
|
+
local entombed_at=$(jq -r '.entombed_at // ""' "$manifest_file" 2>/dev/null)
|
|
264
|
+
|
|
265
|
+
# Escape for JSON
|
|
266
|
+
goal=$(echo "$goal" | jq -Rs '.[:-1]')
|
|
267
|
+
milestone=$(echo "$milestone" | jq -Rs '.[:-1]')
|
|
268
|
+
|
|
269
|
+
# Add comma if not first
|
|
270
|
+
if [[ "$first" == "true" ]]; then
|
|
271
|
+
first=false
|
|
272
|
+
else
|
|
273
|
+
chambers+=","
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
chambers+=$(cat <<EOF
|
|
277
|
+
{
|
|
278
|
+
"name": $(echo "$chamber_name" | jq -Rs '.[:-1]'),
|
|
279
|
+
"goal": $goal,
|
|
280
|
+
"milestone": $milestone,
|
|
281
|
+
"phases_completed": $phases_completed,
|
|
282
|
+
"entombed_at": $(echo "$entombed_at" | jq -Rs '.[:-1]')
|
|
283
|
+
}
|
|
284
|
+
EOF
|
|
285
|
+
)
|
|
286
|
+
done < <(find "$chambers_root" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null || true)
|
|
287
|
+
|
|
288
|
+
chambers+="]"
|
|
289
|
+
|
|
290
|
+
# Sort by entombed_at descending using jq
|
|
291
|
+
local sorted=$(echo "$chambers" | jq 'sort_by(.entombed_at) | reverse')
|
|
292
|
+
|
|
293
|
+
json_ok "$sorted"
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# Export functions for use in other scripts
|
|
297
|
+
export -f chamber_sanitize_goal chamber_compute_hash chamber_create chamber_verify chamber_list
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Colorized activity log stream for tmux watch pane
|
|
3
|
+
# Usage: bash colorize-log.sh [log_file]
|
|
4
|
+
|
|
5
|
+
LOG_FILE="${1:-.aether/data/activity.log}"
|
|
6
|
+
|
|
7
|
+
# ANSI color codes by caste (as per V2 improvement plan)
|
|
8
|
+
QUEEN='\033[35m' # Magenta
|
|
9
|
+
BUILDER='\033[33m' # Yellow
|
|
10
|
+
WATCHER='\033[36m' # Cyan
|
|
11
|
+
SCOUT='\033[32m' # Green
|
|
12
|
+
COLONIZER='\033[34m' # Blue
|
|
13
|
+
ARCHITECT='\033[37m' # White
|
|
14
|
+
|
|
15
|
+
# Action colors (bright variants)
|
|
16
|
+
SPAWN='\033[93m' # Bright Yellow
|
|
17
|
+
COMPLETE='\033[92m' # Bright Green
|
|
18
|
+
ERROR='\033[91m' # Bright Red
|
|
19
|
+
CREATED='\033[96m' # Bright Cyan
|
|
20
|
+
MODIFIED='\033[94m' # Bright Blue
|
|
21
|
+
|
|
22
|
+
# Base colors
|
|
23
|
+
YELLOW='\033[33m'
|
|
24
|
+
GREEN='\033[32m'
|
|
25
|
+
RED='\033[31m'
|
|
26
|
+
CYAN='\033[36m'
|
|
27
|
+
MAGENTA='\033[35m'
|
|
28
|
+
BLUE='\033[34m'
|
|
29
|
+
BOLD='\033[1m'
|
|
30
|
+
DIM='\033[2m'
|
|
31
|
+
RESET='\033[0m'
|
|
32
|
+
|
|
33
|
+
# Get caste color from ant name patterns
|
|
34
|
+
get_caste_color() {
|
|
35
|
+
case "$1" in
|
|
36
|
+
*Queen*|*QUEEN*)
|
|
37
|
+
echo "$QUEEN"
|
|
38
|
+
;;
|
|
39
|
+
*Builder*|*Bolt*|*Hammer*|*Forge*|*Mason*|*Brick*|*Anvil*|*Weld*)
|
|
40
|
+
echo "$BUILDER"
|
|
41
|
+
;;
|
|
42
|
+
*Watcher*|*Vigil*|*Sentinel*|*Guard*|*Keen*|*Sharp*|*Hawk*|*Watch*|*Alert*)
|
|
43
|
+
echo "$WATCHER"
|
|
44
|
+
;;
|
|
45
|
+
*Scout*|*Swift*|*Dash*|*Ranger*|*Track*|*Seek*|*Path*|*Roam*|*Quest*)
|
|
46
|
+
echo "$SCOUT"
|
|
47
|
+
;;
|
|
48
|
+
*Colonizer*|*Pioneer*|*Map*|*Chart*|*Venture*|*Explore*|*Compass*|*Atlas*|*Trek*)
|
|
49
|
+
echo "$COLONIZER"
|
|
50
|
+
;;
|
|
51
|
+
*Architect*|*Blueprint*|*Draft*|*Design*|*Plan*|*Schema*|*Frame*|*Sketch*|*Model*)
|
|
52
|
+
echo "$ARCHITECT"
|
|
53
|
+
;;
|
|
54
|
+
*Prime*|*Alpha*|*Lead*|*Chief*|*First*|*Core*|*Apex*|*Crown*)
|
|
55
|
+
echo "$MAGENTA"
|
|
56
|
+
;;
|
|
57
|
+
*)
|
|
58
|
+
echo "$RESET"
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Get emoji for caste
|
|
64
|
+
get_emoji() {
|
|
65
|
+
case "$1" in
|
|
66
|
+
*Queen*|*QUEEN*) echo "👑" ;;
|
|
67
|
+
*Builder*|*Bolt*|*Hammer*|*Forge*|*Mason*|*Brick*|*Anvil*|*Weld*) echo "🔨" ;;
|
|
68
|
+
*Watcher*|*Vigil*|*Sentinel*|*Guard*|*Keen*|*Sharp*|*Hawk*|*Watch*|*Alert*) echo "👁️" ;;
|
|
69
|
+
*Scout*|*Swift*|*Dash*|*Ranger*|*Track*|*Seek*|*Path*|*Roam*|*Quest*) echo "🔍" ;;
|
|
70
|
+
*Colonizer*|*Pioneer*|*Map*|*Chart*|*Venture*|*Explore*|*Compass*|*Atlas*|*Trek*) echo "🗺️" ;;
|
|
71
|
+
*Architect*|*Blueprint*|*Draft*|*Design*|*Plan*|*Schema*|*Frame*|*Sketch*|*Model*) echo "🏛️" ;;
|
|
72
|
+
*Prime*|*Alpha*|*Lead*|*Chief*|*First*|*Core*|*Apex*|*Crown*) echo "👑" ;;
|
|
73
|
+
*) echo "🐜" ;;
|
|
74
|
+
esac
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Colony header
|
|
78
|
+
echo -e "${BOLD}${MAGENTA}"
|
|
79
|
+
cat << 'EOF'
|
|
80
|
+
.-.
|
|
81
|
+
(o o) AETHER COLONY
|
|
82
|
+
| O | Activity Stream
|
|
83
|
+
`-`
|
|
84
|
+
EOF
|
|
85
|
+
echo -e "${RESET}"
|
|
86
|
+
echo -e "${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
87
|
+
echo ""
|
|
88
|
+
|
|
89
|
+
# Stream and colorize log entries
|
|
90
|
+
tail -f "$LOG_FILE" 2>/dev/null | while IFS= read -r line; do
|
|
91
|
+
# Extract caste color if ant name is in the line
|
|
92
|
+
caste_color=$(get_caste_color "$line")
|
|
93
|
+
emoji=$(get_emoji "$line")
|
|
94
|
+
|
|
95
|
+
case "$line" in
|
|
96
|
+
*SPAWN*)
|
|
97
|
+
printf "${SPAWN}⚡ %s${RESET}\n" "$line"
|
|
98
|
+
;;
|
|
99
|
+
*COMPLETE*|*completed*)
|
|
100
|
+
printf "${COMPLETE}✅ %s${RESET}\n" "$line"
|
|
101
|
+
;;
|
|
102
|
+
*ERROR*|*FAILED*|*failed*)
|
|
103
|
+
printf "${ERROR}${BOLD}❌ %s${RESET}\n" "$line"
|
|
104
|
+
;;
|
|
105
|
+
*CREATED*)
|
|
106
|
+
printf "${caste_color}✨ %s${RESET}\n" "$line"
|
|
107
|
+
;;
|
|
108
|
+
*MODIFIED*)
|
|
109
|
+
printf "${caste_color}📝 %s${RESET}\n" "$line"
|
|
110
|
+
;;
|
|
111
|
+
*RESEARCH*|*EXPLORING*)
|
|
112
|
+
printf "${caste_color}🔬 %s${RESET}\n" "$line"
|
|
113
|
+
;;
|
|
114
|
+
*EXECUTING*|*BUILDING*)
|
|
115
|
+
printf "${caste_color}⚙️ %s${RESET}\n" "$line"
|
|
116
|
+
;;
|
|
117
|
+
"# Phase"*)
|
|
118
|
+
# Phase headers get special treatment
|
|
119
|
+
printf "\n${BOLD}${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"
|
|
120
|
+
printf "${BOLD}${MAGENTA}🐜 %s${RESET}\n" "$line"
|
|
121
|
+
printf "${BOLD}${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"
|
|
122
|
+
;;
|
|
123
|
+
*)
|
|
124
|
+
# Apply caste color if detected, otherwise default
|
|
125
|
+
if [[ "$caste_color" != "$RESET" ]]; then
|
|
126
|
+
printf "${caste_color}%s %s${RESET}\n" "$emoji" "$line"
|
|
127
|
+
else
|
|
128
|
+
echo "$line"
|
|
129
|
+
fi
|
|
130
|
+
;;
|
|
131
|
+
esac
|
|
132
|
+
done
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Aether Colony Error Handler Module
|
|
3
|
+
# Structured JSON error handling for bash utilities
|
|
4
|
+
#
|
|
5
|
+
# Usage: source "$SCRIPT_DIR/utils/error-handler.sh"
|
|
6
|
+
#
|
|
7
|
+
# Provides consistent error format between Node.js CLI and bash utilities
|
|
8
|
+
|
|
9
|
+
# --- Error Code Constants (matching Node.js error codes) ---
|
|
10
|
+
E_UNKNOWN="E_UNKNOWN"
|
|
11
|
+
E_HUB_NOT_FOUND="E_HUB_NOT_FOUND"
|
|
12
|
+
E_REPO_NOT_INITIALIZED="E_REPO_NOT_INITIALIZED"
|
|
13
|
+
E_FILE_NOT_FOUND="E_FILE_NOT_FOUND"
|
|
14
|
+
E_JSON_INVALID="E_JSON_INVALID"
|
|
15
|
+
E_LOCK_FAILED="E_LOCK_FAILED"
|
|
16
|
+
E_LOCK_STALE="E_LOCK_STALE"
|
|
17
|
+
E_GIT_ERROR="E_GIT_ERROR"
|
|
18
|
+
E_VALIDATION_FAILED="E_VALIDATION_FAILED"
|
|
19
|
+
E_FEATURE_UNAVAILABLE="E_FEATURE_UNAVAILABLE"
|
|
20
|
+
E_BASH_ERROR="E_BASH_ERROR"
|
|
21
|
+
E_DEPENDENCY_MISSING="E_DEPENDENCY_MISSING"
|
|
22
|
+
E_RESOURCE_NOT_FOUND="E_RESOURCE_NOT_FOUND"
|
|
23
|
+
|
|
24
|
+
# --- Recovery Suggestion Functions (internal, prefixed with _) ---
|
|
25
|
+
_recovery_hub_not_found() { echo '"Run: aether install"'; }
|
|
26
|
+
_recovery_repo_not_init() { echo '"Run /ant:init in this repo first"'; }
|
|
27
|
+
_recovery_file_not_found() { echo '"Check file path and permissions"'; }
|
|
28
|
+
_recovery_json_invalid() { echo '"Validate JSON syntax"'; }
|
|
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"'; }
|
|
31
|
+
_recovery_git_error() { echo '"Check git status and resolve conflicts"'; }
|
|
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"'; }
|
|
35
|
+
|
|
36
|
+
# Get recovery suggestion based on error code
|
|
37
|
+
_get_recovery() {
|
|
38
|
+
local code="$1"
|
|
39
|
+
case "$code" in
|
|
40
|
+
"$E_HUB_NOT_FOUND") _recovery_hub_not_found ;;
|
|
41
|
+
"$E_REPO_NOT_INITIALIZED") _recovery_repo_not_init ;;
|
|
42
|
+
"$E_FILE_NOT_FOUND") _recovery_file_not_found ;;
|
|
43
|
+
"$E_JSON_INVALID") _recovery_json_invalid ;;
|
|
44
|
+
"$E_LOCK_FAILED") _recovery_lock_failed ;;
|
|
45
|
+
"$E_LOCK_STALE") _recovery_lock_stale ;;
|
|
46
|
+
"$E_GIT_ERROR") _recovery_git_error ;;
|
|
47
|
+
"$E_DEPENDENCY_MISSING") _recovery_dependency_missing ;;
|
|
48
|
+
"$E_RESOURCE_NOT_FOUND") _recovery_resource_not_found ;;
|
|
49
|
+
*) _recovery_default ;;
|
|
50
|
+
esac
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# --- Enhanced json_err function ---
|
|
54
|
+
# Signature: json_err [code] [message] [details] [recovery]
|
|
55
|
+
# All parameters optional with sensible defaults
|
|
56
|
+
json_err() {
|
|
57
|
+
local code="${1:-$E_UNKNOWN}"
|
|
58
|
+
local message="${2:-An unknown error occurred}"
|
|
59
|
+
local details="${3:-null}"
|
|
60
|
+
local recovery="${4:-}"
|
|
61
|
+
local timestamp
|
|
62
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
63
|
+
|
|
64
|
+
# Get recovery suggestion if not provided
|
|
65
|
+
if [[ -z "$recovery" ]]; then
|
|
66
|
+
recovery=$(_get_recovery "$code")
|
|
67
|
+
else
|
|
68
|
+
# Escape and quote the recovery string
|
|
69
|
+
recovery=$(echo "$recovery" | sed 's/"/\\"/g' | tr '\n' ' ')
|
|
70
|
+
recovery="\"$recovery\""
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Escape message for JSON
|
|
74
|
+
local escaped_message
|
|
75
|
+
escaped_message=$(echo "$message" | sed 's/"/\\"/g' | tr '\n' ' ')
|
|
76
|
+
|
|
77
|
+
# Build details JSON
|
|
78
|
+
local details_json
|
|
79
|
+
if [[ "$details" == "null" || -z "$details" ]]; then
|
|
80
|
+
details_json="null"
|
|
81
|
+
else
|
|
82
|
+
details_json="$details"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Output structured JSON to stderr
|
|
86
|
+
printf '{"ok":false,"error":{"code":"%s","message":"%s","details":%s,"recovery":%s,"timestamp":"%s"}}\n' \
|
|
87
|
+
"$code" "$escaped_message" "$details_json" "$recovery" "$timestamp" >&2
|
|
88
|
+
|
|
89
|
+
# Log to activity.log (best effort)
|
|
90
|
+
if [[ -n "${DATA_DIR:-}" ]]; then
|
|
91
|
+
echo "[$timestamp] ERROR $code: $escaped_message" >> "$DATA_DIR/activity.log" 2>/dev/null || true
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
exit 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# --- json_warn function for non-fatal warnings ---
|
|
98
|
+
# Signature: json_warn [code] [message]
|
|
99
|
+
json_warn() {
|
|
100
|
+
local code="${1:-W_UNKNOWN}"
|
|
101
|
+
local message="${2:-Warning}"
|
|
102
|
+
local timestamp
|
|
103
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
104
|
+
|
|
105
|
+
# Escape message for JSON
|
|
106
|
+
local escaped_message
|
|
107
|
+
escaped_message=$(echo "$message" | sed 's/"/\\"/g' | tr '\n' ' ')
|
|
108
|
+
|
|
109
|
+
# Output warning JSON to stdout (not stderr - this is non-fatal)
|
|
110
|
+
printf '{"ok":true,"warning":{"code":"%s","message":"%s","timestamp":"%s"}}\n' \
|
|
111
|
+
"$code" "$escaped_message" "$timestamp"
|
|
112
|
+
|
|
113
|
+
# Log to activity.log (best effort)
|
|
114
|
+
if [[ -n "${DATA_DIR:-}" ]]; then
|
|
115
|
+
echo "[$timestamp] WARN $code: $escaped_message" >> "$DATA_DIR/activity.log" 2>/dev/null || true
|
|
116
|
+
fi
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# --- error_handler function for trap ERR ---
|
|
120
|
+
# Captures: line number, command, exit code
|
|
121
|
+
# Usage: trap 'error_handler ${LINENO} "$BASH_COMMAND" $?' ERR
|
|
122
|
+
error_handler() {
|
|
123
|
+
local line_num="${1:-unknown}"
|
|
124
|
+
local command="${2:-unknown}"
|
|
125
|
+
local exit_code="${3:-1}"
|
|
126
|
+
local timestamp
|
|
127
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
128
|
+
|
|
129
|
+
# Escape command for JSON
|
|
130
|
+
local escaped_command
|
|
131
|
+
escaped_command=$(echo "$command" | sed 's/"/\\"/g' | tr '\n' ' ')
|
|
132
|
+
|
|
133
|
+
# Build details JSON
|
|
134
|
+
local details
|
|
135
|
+
details="{\"line\":$line_num,\"command\":\"$escaped_command\",\"exit_code\":$exit_code}"
|
|
136
|
+
|
|
137
|
+
# Output structured JSON to stderr
|
|
138
|
+
printf '{"ok":false,"error":{"code":"%s","message":"Bash command failed","details":%s,"recovery":%s,"timestamp":"%s"}}\n' \
|
|
139
|
+
"$E_BASH_ERROR" "$details" "$(_recovery_default)" "$timestamp" >&2
|
|
140
|
+
|
|
141
|
+
# Log to activity.log (best effort)
|
|
142
|
+
if [[ -n "${DATA_DIR:-}" ]]; then
|
|
143
|
+
echo "[$timestamp] ERROR $E_BASH_ERROR: Command failed at line $line_num (exit $exit_code)" >> "$DATA_DIR/activity.log" 2>/dev/null || true
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
exit 1
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# --- Feature flag functions for graceful degradation ---
|
|
150
|
+
# Using simple variables for bash 3.2+ compatibility (no associative arrays)
|
|
151
|
+
|
|
152
|
+
# Track disabled features as colon-separated list: "feature1:reason1|feature2:reason2"
|
|
153
|
+
_FEATURES_DISABLED=""
|
|
154
|
+
|
|
155
|
+
# Enable a feature (remove from disabled list if present)
|
|
156
|
+
feature_enable() {
|
|
157
|
+
local name="$1"
|
|
158
|
+
# Remove from disabled list if present
|
|
159
|
+
_FEATURES_DISABLED=$(echo "$_FEATURES_DISABLED" | sed "s/:$name:[^|]*//g" | sed 's/^|//;s/|$//')
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Disable a feature with reason
|
|
163
|
+
feature_disable() {
|
|
164
|
+
local name="$1"
|
|
165
|
+
local reason="${2:-disabled}"
|
|
166
|
+
# Remove existing entry if present, then add new
|
|
167
|
+
_FEATURES_DISABLED=$(echo "$_FEATURES_DISABLED" | sed "s/:$name:[^|]*//g")
|
|
168
|
+
if [[ -z "$_FEATURES_DISABLED" ]]; then
|
|
169
|
+
_FEATURES_DISABLED=":$name:$reason"
|
|
170
|
+
else
|
|
171
|
+
_FEATURES_DISABLED="${_FEATURES_DISABLED}|:$name:$reason"
|
|
172
|
+
fi
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Check if feature is enabled (returns 0 if enabled, 1 if disabled)
|
|
176
|
+
feature_enabled() {
|
|
177
|
+
local name="$1"
|
|
178
|
+
if echo "$_FEATURES_DISABLED" | grep -q ":$name:"; then
|
|
179
|
+
return 1
|
|
180
|
+
fi
|
|
181
|
+
return 0
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Get reason for feature being disabled
|
|
185
|
+
_feature_reason() {
|
|
186
|
+
local name="$1"
|
|
187
|
+
echo "$_FEATURES_DISABLED" | grep -o ":$name:[^|]*" | sed "s/:$name://" || echo "unknown"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
# Log degradation warning
|
|
191
|
+
feature_log_degradation() {
|
|
192
|
+
local name="$1"
|
|
193
|
+
local reason="${2:-}"
|
|
194
|
+
if [[ -z "$reason" ]]; then
|
|
195
|
+
reason=$(_feature_reason "$name")
|
|
196
|
+
fi
|
|
197
|
+
json_warn "W_DEGRADED" "Feature '$name' is disabled: $reason"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# --- Export all functions and variables ---
|
|
201
|
+
export -f json_err json_warn error_handler
|
|
202
|
+
export -f feature_enable feature_disable feature_enabled feature_log_degradation
|
|
203
|
+
export -f _get_recovery _recovery_hub_not_found _recovery_repo_not_init
|
|
204
|
+
export -f _recovery_file_not_found _recovery_json_invalid _recovery_lock_failed
|
|
205
|
+
export -f _recovery_lock_stale
|
|
206
|
+
export -f _recovery_git_error _recovery_default _feature_reason
|
|
207
|
+
export -f _recovery_dependency_missing _recovery_resource_not_found
|
|
208
|
+
export E_UNKNOWN E_HUB_NOT_FOUND E_REPO_NOT_INITIALIZED E_FILE_NOT_FOUND
|
|
209
|
+
export E_JSON_INVALID E_LOCK_FAILED E_LOCK_STALE E_GIT_ERROR E_VALIDATION_FAILED
|
|
210
|
+
export E_FEATURE_UNAVAILABLE E_BASH_ERROR
|
|
211
|
+
export E_DEPENDENCY_MISSING E_RESOURCE_NOT_FOUND
|
|
212
|
+
export _FEATURES_DISABLED
|