aether-colony 5.0.0 → 5.2.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 +3226 -3345
- package/.aether/agents-claude/aether-ambassador.md +265 -0
- package/.aether/agents-claude/aether-archaeologist.md +327 -0
- package/.aether/agents-claude/aether-architect.md +236 -0
- package/.aether/agents-claude/aether-auditor.md +271 -0
- package/.aether/agents-claude/aether-builder.md +224 -0
- package/.aether/agents-claude/aether-chaos.md +269 -0
- package/.aether/agents-claude/aether-chronicler.md +305 -0
- package/.aether/agents-claude/aether-gatekeeper.md +330 -0
- package/.aether/agents-claude/aether-includer.md +374 -0
- package/.aether/agents-claude/aether-keeper.md +272 -0
- package/.aether/agents-claude/aether-measurer.md +322 -0
- package/.aether/agents-claude/aether-oracle.md +237 -0
- package/.aether/agents-claude/aether-probe.md +211 -0
- package/.aether/agents-claude/aether-queen.md +330 -0
- package/.aether/agents-claude/aether-route-setter.md +178 -0
- package/.aether/agents-claude/aether-sage.md +418 -0
- package/.aether/agents-claude/aether-scout.md +179 -0
- package/.aether/agents-claude/aether-surveyor-disciplines.md +417 -0
- package/.aether/agents-claude/aether-surveyor-nest.md +355 -0
- package/.aether/agents-claude/aether-surveyor-pathogens.md +289 -0
- package/.aether/agents-claude/aether-surveyor-provisions.md +360 -0
- package/.aether/agents-claude/aether-tracker.md +270 -0
- package/.aether/agents-claude/aether-watcher.md +280 -0
- package/.aether/agents-claude/aether-weaver.md +248 -0
- package/.aether/commands/archaeology.yaml +653 -0
- package/.aether/commands/build.yaml +1221 -0
- package/.aether/commands/chaos.yaml +653 -0
- package/.aether/commands/colonize.yaml +442 -0
- package/.aether/commands/continue.yaml +1484 -0
- package/.aether/commands/council.yaml +509 -0
- package/.aether/commands/data-clean.yaml +80 -0
- package/.aether/commands/dream.yaml +275 -0
- package/.aether/commands/entomb.yaml +863 -0
- package/.aether/commands/export-signals.yaml +64 -0
- package/.aether/commands/feedback.yaml +158 -0
- package/.aether/commands/flag.yaml +160 -0
- package/.aether/commands/flags.yaml +177 -0
- package/.aether/commands/focus.yaml +112 -0
- package/.aether/commands/help.yaml +167 -0
- package/.aether/commands/history.yaml +137 -0
- package/.aether/commands/import-signals.yaml +79 -0
- package/.aether/commands/init.yaml +502 -0
- package/.aether/commands/insert-phase.yaml +102 -0
- package/.aether/commands/interpret.yaml +285 -0
- package/.aether/commands/lay-eggs.yaml +224 -0
- package/.aether/commands/maturity.yaml +122 -0
- package/.aether/commands/memory-details.yaml +74 -0
- package/.aether/commands/migrate-state.yaml +174 -0
- package/.aether/commands/oracle.yaml +1224 -0
- package/.aether/commands/organize.yaml +446 -0
- package/.aether/commands/patrol.yaml +621 -0
- package/.aether/commands/pause-colony.yaml +424 -0
- package/.aether/commands/phase.yaml +124 -0
- package/.aether/commands/pheromones.yaml +153 -0
- package/.aether/commands/plan.yaml +1364 -0
- package/.aether/commands/preferences.yaml +63 -0
- package/.aether/commands/quick.yaml +104 -0
- package/.aether/commands/redirect.yaml +123 -0
- package/.aether/commands/resume-colony.yaml +375 -0
- package/.aether/commands/resume.yaml +407 -0
- package/.aether/commands/run.yaml +229 -0
- package/.aether/commands/seal.yaml +1214 -0
- package/.aether/commands/skill-create.yaml +337 -0
- package/.aether/commands/status.yaml +408 -0
- package/.aether/commands/swarm.yaml +352 -0
- package/.aether/commands/tunnels.yaml +814 -0
- package/.aether/commands/update.yaml +131 -0
- package/.aether/commands/verify-castes.yaml +159 -0
- package/.aether/commands/watch.yaml +454 -0
- package/.aether/docs/INCIDENT_TEMPLATE.md +32 -0
- package/.aether/docs/QUEEN-SYSTEM.md +11 -11
- package/.aether/docs/README.md +32 -2
- package/.aether/docs/command-playbooks/README.md +23 -0
- package/.aether/docs/command-playbooks/build-complete.md +349 -0
- package/.aether/docs/command-playbooks/build-context.md +282 -0
- package/.aether/docs/command-playbooks/build-full.md +1683 -0
- package/.aether/docs/command-playbooks/build-prep.md +284 -0
- package/.aether/docs/command-playbooks/build-verify.md +405 -0
- package/.aether/docs/command-playbooks/build-wave.md +749 -0
- package/.aether/docs/command-playbooks/continue-advance.md +524 -0
- package/.aether/docs/command-playbooks/continue-finalize.md +447 -0
- package/.aether/docs/command-playbooks/continue-full.md +1725 -0
- package/.aether/docs/command-playbooks/continue-gates.md +686 -0
- package/.aether/docs/command-playbooks/continue-verify.md +407 -0
- package/.aether/docs/context-continuity.md +84 -0
- package/.aether/docs/disciplines/DISCIPLINES.md +9 -7
- package/.aether/docs/error-codes.md +1 -1
- package/.aether/docs/known-issues.md +34 -173
- package/.aether/docs/pheromones.md +86 -6
- package/.aether/docs/plans/pheromone-display-plan.md +257 -0
- package/.aether/docs/queen-commands.md +10 -9
- package/.aether/docs/source-of-truth-map.md +132 -0
- package/.aether/docs/xml-utilities.md +47 -0
- package/.aether/rules/aether-colony.md +23 -13
- package/.aether/scripts/incident-test-add.sh +47 -0
- package/.aether/scripts/weekly-audit.sh +79 -0
- package/.aether/skills/.index.json +649 -0
- package/.aether/skills/colony/.manifest.json +16 -0
- package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
- package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
- package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
- package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
- package/.aether/skills/colony/context-management/SKILL.md +80 -0
- package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
- package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
- package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
- package/.aether/skills/colony/state-safety/SKILL.md +84 -0
- package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
- package/.aether/skills/domain/.manifest.json +24 -0
- package/.aether/skills/domain/README.md +33 -0
- package/.aether/skills/domain/django/SKILL.md +49 -0
- package/.aether/skills/domain/docker/SKILL.md +52 -0
- package/.aether/skills/domain/golang/SKILL.md +52 -0
- package/.aether/skills/domain/graphql/SKILL.md +51 -0
- package/.aether/skills/domain/html-css/SKILL.md +48 -0
- package/.aether/skills/domain/nextjs/SKILL.md +45 -0
- package/.aether/skills/domain/nodejs/SKILL.md +53 -0
- package/.aether/skills/domain/postgresql/SKILL.md +53 -0
- package/.aether/skills/domain/prisma/SKILL.md +59 -0
- package/.aether/skills/domain/python/SKILL.md +50 -0
- package/.aether/skills/domain/rails/SKILL.md +52 -0
- package/.aether/skills/domain/react/SKILL.md +45 -0
- package/.aether/skills/domain/rest-api/SKILL.md +58 -0
- package/.aether/skills/domain/svelte/SKILL.md +47 -0
- package/.aether/skills/domain/tailwind/SKILL.md +45 -0
- package/.aether/skills/domain/testing/SKILL.md +53 -0
- package/.aether/skills/domain/typescript/SKILL.md +58 -0
- package/.aether/skills/domain/vue/SKILL.md +49 -0
- package/.aether/templates/QUEEN.md.template +23 -41
- package/.aether/templates/colony-state-reset.jq.template +1 -0
- package/.aether/templates/colony-state.template.json +4 -0
- package/.aether/templates/learning-observations.template.json +6 -0
- package/.aether/templates/midden.template.json +13 -0
- package/.aether/templates/pheromones.template.json +6 -0
- package/.aether/templates/session.template.json +9 -0
- package/.aether/utils/atomic-write.sh +63 -17
- package/.aether/utils/chamber-utils.sh +145 -2
- package/.aether/utils/council.sh +425 -0
- package/.aether/utils/emoji-audit.sh +166 -0
- package/.aether/utils/error-handler.sh +21 -7
- package/.aether/utils/file-lock.sh +182 -27
- package/.aether/utils/flag.sh +278 -0
- package/.aether/utils/hive.sh +572 -0
- package/.aether/utils/immune.sh +508 -0
- package/.aether/utils/learning.sh +1928 -0
- package/.aether/utils/midden.sh +520 -0
- package/.aether/utils/oracle/oracle.md +168 -0
- package/.aether/utils/oracle/oracle.sh +1023 -0
- package/.aether/utils/pheromone.sh +2029 -0
- package/.aether/utils/queen.sh +1710 -0
- package/.aether/utils/scan.sh +860 -0
- package/.aether/utils/semantic-cli.sh +10 -8
- package/.aether/utils/session.sh +816 -0
- package/.aether/utils/skills.sh +509 -0
- package/.aether/utils/spawn-tree.sh +103 -271
- package/.aether/utils/spawn.sh +260 -0
- package/.aether/utils/state-api.sh +389 -0
- package/.aether/utils/state-loader.sh +8 -6
- package/.aether/utils/suggest.sh +611 -0
- package/.aether/utils/swarm-display.sh +10 -1
- package/.aether/utils/swarm.sh +1004 -0
- package/.aether/utils/watch-spawn-tree.sh +11 -2
- package/.aether/utils/xml-compose.sh +2 -2
- package/.aether/utils/xml-convert.sh +9 -5
- package/.aether/utils/xml-core.sh +5 -9
- package/.aether/utils/xml-query.sh +4 -4
- package/.aether/workers.md +86 -67
- package/.claude/agents/ant/aether-ambassador.md +2 -1
- package/.claude/agents/ant/aether-archaeologist.md +6 -1
- package/.claude/agents/ant/aether-architect.md +236 -0
- package/.claude/agents/ant/aether-auditor.md +6 -1
- package/.claude/agents/ant/aether-builder.md +38 -1
- package/.claude/agents/ant/aether-chaos.md +2 -1
- package/.claude/agents/ant/aether-chronicler.md +1 -0
- package/.claude/agents/ant/aether-gatekeeper.md +6 -1
- package/.claude/agents/ant/aether-includer.md +1 -0
- package/.claude/agents/ant/aether-keeper.md +1 -0
- package/.claude/agents/ant/aether-measurer.md +6 -1
- package/.claude/agents/ant/aether-oracle.md +237 -0
- package/.claude/agents/ant/aether-probe.md +2 -1
- package/.claude/agents/ant/aether-queen.md +6 -1
- package/.claude/agents/ant/aether-route-setter.md +6 -1
- package/.claude/agents/ant/aether-sage.md +68 -3
- package/.claude/agents/ant/aether-scout.md +38 -1
- package/.claude/agents/ant/aether-surveyor-disciplines.md +2 -1
- package/.claude/agents/ant/aether-surveyor-nest.md +2 -1
- package/.claude/agents/ant/aether-surveyor-pathogens.md +2 -1
- package/.claude/agents/ant/aether-surveyor-provisions.md +2 -1
- package/.claude/agents/ant/aether-tracker.md +6 -1
- package/.claude/agents/ant/aether-watcher.md +37 -1
- package/.claude/agents/ant/aether-weaver.md +2 -1
- package/.claude/commands/ant/archaeology.md +1 -8
- package/.claude/commands/ant/build.md +43 -1159
- package/.claude/commands/ant/chaos.md +1 -14
- package/.claude/commands/ant/colonize.md +3 -14
- package/.claude/commands/ant/continue.md +40 -1026
- package/.claude/commands/ant/council.md +213 -15
- package/.claude/commands/ant/data-clean.md +81 -0
- package/.claude/commands/ant/dream.md +12 -9
- package/.claude/commands/ant/entomb.md +62 -87
- package/.claude/commands/ant/export-signals.md +57 -0
- package/.claude/commands/ant/feedback.md +18 -0
- package/.claude/commands/ant/flag.md +12 -0
- package/.claude/commands/ant/flags.md +22 -8
- package/.claude/commands/ant/focus.md +18 -0
- package/.claude/commands/ant/help.md +40 -8
- package/.claude/commands/ant/history.md +3 -0
- package/.claude/commands/ant/import-signals.md +71 -0
- package/.claude/commands/ant/init.md +349 -191
- package/.claude/commands/ant/insert-phase.md +105 -0
- package/.claude/commands/ant/interpret.md +11 -0
- package/.claude/commands/ant/lay-eggs.md +167 -158
- package/.claude/commands/ant/maturity.md +22 -11
- package/.claude/commands/ant/memory-details.md +77 -0
- package/.claude/commands/ant/migrate-state.md +6 -0
- package/.claude/commands/ant/oracle.md +317 -62
- package/.claude/commands/ant/organize.md +10 -5
- package/.claude/commands/ant/patrol.md +620 -0
- package/.claude/commands/ant/pause-colony.md +8 -22
- package/.claude/commands/ant/phase.md +26 -37
- package/.claude/commands/ant/pheromones.md +156 -0
- package/.claude/commands/ant/plan.md +199 -50
- package/.claude/commands/ant/preferences.md +65 -0
- package/.claude/commands/ant/quick.md +100 -0
- package/.claude/commands/ant/redirect.md +18 -0
- package/.claude/commands/ant/resume-colony.md +37 -22
- package/.claude/commands/ant/resume.md +60 -7
- package/.claude/commands/ant/run.md +231 -0
- package/.claude/commands/ant/seal.md +506 -78
- package/.claude/commands/ant/skill-create.md +286 -0
- package/.claude/commands/ant/status.md +171 -1
- package/.claude/commands/ant/swarm.md +11 -23
- package/.claude/commands/ant/tunnels.md +1 -0
- package/.claude/commands/ant/update.md +58 -135
- package/.claude/commands/ant/verify-castes.md +90 -42
- package/.claude/commands/ant/watch.md +1 -0
- package/.opencode/agents/aether-ambassador.md +1 -1
- package/.opencode/agents/aether-architect.md +133 -0
- package/.opencode/agents/aether-builder.md +3 -3
- package/.opencode/agents/aether-oracle.md +137 -0
- package/.opencode/agents/aether-queen.md +1 -1
- package/.opencode/agents/aether-route-setter.md +1 -1
- package/.opencode/agents/aether-scout.md +1 -1
- package/.opencode/agents/aether-surveyor-disciplines.md +6 -1
- package/.opencode/agents/aether-surveyor-nest.md +6 -1
- package/.opencode/agents/aether-surveyor-pathogens.md +6 -1
- package/.opencode/agents/aether-surveyor-provisions.md +6 -1
- package/.opencode/agents/aether-tracker.md +1 -1
- package/.opencode/agents/aether-watcher.md +1 -1
- package/.opencode/agents/aether-weaver.md +1 -1
- package/.opencode/commands/ant/archaeology.md +7 -14
- package/.opencode/commands/ant/build.md +54 -88
- package/.opencode/commands/ant/chaos.md +7 -24
- package/.opencode/commands/ant/colonize.md +10 -17
- package/.opencode/commands/ant/continue.md +595 -66
- package/.opencode/commands/ant/council.md +150 -18
- package/.opencode/commands/ant/data-clean.md +77 -0
- package/.opencode/commands/ant/dream.md +15 -17
- package/.opencode/commands/ant/entomb.md +28 -18
- package/.opencode/commands/ant/export-signals.md +54 -0
- package/.opencode/commands/ant/feedback.md +24 -5
- package/.opencode/commands/ant/flag.md +16 -4
- package/.opencode/commands/ant/flags.md +24 -10
- package/.opencode/commands/ant/focus.md +22 -5
- package/.opencode/commands/ant/help.md +41 -8
- package/.opencode/commands/ant/history.md +9 -0
- package/.opencode/commands/ant/import-signals.md +68 -0
- package/.opencode/commands/ant/init.md +396 -154
- package/.opencode/commands/ant/insert-phase.md +111 -0
- package/.opencode/commands/ant/interpret.md +16 -0
- package/.opencode/commands/ant/lay-eggs.md +184 -112
- package/.opencode/commands/ant/maturity.md +18 -2
- package/.opencode/commands/ant/memory-details.md +83 -0
- package/.opencode/commands/ant/migrate-state.md +12 -0
- package/.opencode/commands/ant/oracle.md +322 -67
- package/.opencode/commands/ant/organize.md +14 -12
- package/.opencode/commands/ant/patrol.md +626 -0
- package/.opencode/commands/ant/pause-colony.md +12 -29
- package/.opencode/commands/ant/phase.md +30 -40
- package/.opencode/commands/ant/pheromones.md +162 -0
- package/.opencode/commands/ant/plan.md +210 -57
- package/.opencode/commands/ant/preferences.md +71 -0
- package/.opencode/commands/ant/quick.md +91 -0
- package/.opencode/commands/ant/redirect.md +22 -5
- package/.opencode/commands/ant/resume-colony.md +41 -29
- package/.opencode/commands/ant/resume.md +80 -20
- package/.opencode/commands/ant/run.md +237 -0
- package/.opencode/commands/ant/seal.md +230 -25
- package/.opencode/commands/ant/skill-create.md +63 -0
- package/.opencode/commands/ant/status.md +125 -30
- package/.opencode/commands/ant/swarm.md +3 -345
- package/.opencode/commands/ant/tunnels.md +3 -9
- package/.opencode/commands/ant/update.md +63 -127
- package/.opencode/commands/ant/verify-castes.md +96 -42
- package/.opencode/commands/ant/watch.md +7 -0
- package/CHANGELOG.md +368 -1
- package/README.md +195 -324
- package/bin/cli.js +236 -429
- package/bin/generate-commands.js +186 -0
- package/bin/generate-commands.sh +128 -89
- package/bin/lib/spawn-logger.js +0 -15
- package/bin/lib/update-transaction.js +285 -35
- package/bin/npx-install.js +178 -0
- package/bin/validate-package.sh +85 -3
- package/package.json +16 -4
- package/.aether/CONTEXT.md +0 -160
- package/.aether/docs/QUEEN.md +0 -84
- package/.aether/exchange/colony-registry.xml +0 -11
- package/.aether/exchange/pheromones.xml +0 -87
- package/.aether/exchange/queen-wisdom.xml +0 -14
- package/.aether/model-profiles.yaml +0 -100
- package/.aether/utils/spawn-with-model.sh +0 -56
- package/bin/lib/model-profiles.js +0 -445
- package/bin/lib/model-verify.js +0 -288
- package/bin/lib/proxy-health.js +0 -253
- package/bin/lib/telemetry.js +0 -441
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Spawn utility functions — extracted from aether-utils.sh
|
|
3
|
+
# Provides: _spawn_log, _spawn_complete, _spawn_can_spawn, _spawn_get_depth, _spawn_can_spawn_swarm, _spawn_tree_load, _spawn_tree_active, _spawn_tree_depth, _spawn_efficiency
|
|
4
|
+
#
|
|
5
|
+
# These functions are sourced by aether-utils.sh at startup.
|
|
6
|
+
# All shared infrastructure (json_ok, json_err, json_warn, atomic_write, acquire_lock,
|
|
7
|
+
# release_lock, feature_enabled, LOCK_DIR, DATA_DIR, SCRIPT_DIR, error constants) is available.
|
|
8
|
+
# Note: get_caste_emoji is defined in the main file and available to this module at call time.
|
|
9
|
+
|
|
10
|
+
_spawn_log() {
|
|
11
|
+
# Usage: spawn-log <parent_id> <child_caste> <child_name> <task_summary> [model] [status]
|
|
12
|
+
parent_id="${1:-}"
|
|
13
|
+
child_caste="${2:-}"
|
|
14
|
+
child_name="${3:-}"
|
|
15
|
+
task_summary="${4:-}"
|
|
16
|
+
model="${5:-default}"
|
|
17
|
+
status="${6:-spawned}"
|
|
18
|
+
# Model slot resolution removed (archived: .aether/archive/model-routing/)
|
|
19
|
+
# Agent frontmatter model: fields handle routing natively via Claude Code.
|
|
20
|
+
[[ "$model" == "default" ]] && model="inherit"
|
|
21
|
+
[[ -z "$parent_id" || -z "$child_caste" || -z "$task_summary" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-log <parent_id> <child_caste> <child_name> <task_summary> [model] [status]"
|
|
22
|
+
mkdir -p "$COLONY_DATA_DIR"
|
|
23
|
+
ts=$(date -u +"%H:%M:%S")
|
|
24
|
+
ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
25
|
+
emoji=$(get_caste_emoji "$child_caste")
|
|
26
|
+
parent_emoji=$(get_caste_emoji "$parent_id")
|
|
27
|
+
# Log to activity log with spawn format, emojis, and model info
|
|
28
|
+
[[ "${AETHER_TESTING:-}" != "1" ]] && echo "[$ts] ⚡ SPAWN $parent_emoji $parent_id -> $emoji $child_name ($child_caste): $task_summary [model: $model]" >> "$COLONY_DATA_DIR/activity.log"
|
|
29
|
+
# Log to spawn tree file for visualization (NEW FORMAT: includes model field)
|
|
30
|
+
echo "$ts_full|$parent_id|$child_caste|$child_name|$task_summary|$model|$status" >> "$COLONY_DATA_DIR/spawn-tree.txt"
|
|
31
|
+
# Return emoji-formatted result for display (jq-safe: child_name may contain JSON-special chars)
|
|
32
|
+
json_ok "$(jq -n --arg msg "⚡ $emoji $child_name spawned" '$msg')"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_spawn_complete() {
|
|
36
|
+
# Migrated to state-api facade: uses _state_mutate for failed spawn event logging
|
|
37
|
+
# Usage: spawn-complete <ant_name> <status> [summary]
|
|
38
|
+
ant_name="${1:-}"
|
|
39
|
+
status="${2:-completed}"
|
|
40
|
+
summary="${3:-}"
|
|
41
|
+
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-complete <ant_name> <status> [summary]"
|
|
42
|
+
mkdir -p "$COLONY_DATA_DIR"
|
|
43
|
+
ts=$(date -u +"%H:%M:%S")
|
|
44
|
+
ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
45
|
+
emoji=$(get_caste_emoji "$ant_name")
|
|
46
|
+
status_icon="✅"
|
|
47
|
+
[[ "$status" == "failed" ]] && status_icon="❌"
|
|
48
|
+
[[ "$status" == "blocked" ]] && status_icon="🚫"
|
|
49
|
+
[[ "${AETHER_TESTING:-}" != "1" ]] && echo "[$ts] $status_icon $emoji $ant_name: $status${summary:+ - $summary}" >> "$COLONY_DATA_DIR/activity.log"
|
|
50
|
+
# Update spawn tree
|
|
51
|
+
echo "$ts_full|$ant_name|$status|$summary" >> "$COLONY_DATA_DIR/spawn-tree.txt"
|
|
52
|
+
# Log failed spawns to events array as pipe-delimited strings (matching template format)
|
|
53
|
+
if [[ "$status" == "failed" ]] || [[ "$status" == "error" ]]; then
|
|
54
|
+
if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
|
|
55
|
+
SC_EVENT="$ts_full|spawn_failed|$ant_name|${summary:-unknown}" \
|
|
56
|
+
_state_mutate '
|
|
57
|
+
.events += [env.SC_EVENT]
|
|
58
|
+
' >/dev/null 2>&1 || _aether_log_error "Failed to log spawn failure to colony state"
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
# Return emoji-formatted result for display (jq-safe: ant_name/summary may contain JSON-special chars)
|
|
62
|
+
json_ok "$(jq -n --arg msg "$status_icon $emoji $ant_name: ${summary:-$status}" '$msg')"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_spawn_can_spawn() {
|
|
66
|
+
# Check if spawning is allowed at given depth
|
|
67
|
+
# Usage: spawn-can-spawn [depth] [--enforce]
|
|
68
|
+
# Returns: {can_spawn: bool, depth: N, max_spawns: N, current_total: N, global_cap: N}
|
|
69
|
+
# --enforce: fail with non-zero exit when spawning is not allowed
|
|
70
|
+
depth=""
|
|
71
|
+
enforce_mode=false
|
|
72
|
+
for arg in "$@"; do
|
|
73
|
+
case "$arg" in
|
|
74
|
+
--enforce) enforce_mode=true ;;
|
|
75
|
+
*)
|
|
76
|
+
if [[ -z "$depth" ]]; then
|
|
77
|
+
depth="$arg"
|
|
78
|
+
else
|
|
79
|
+
json_err "$E_VALIDATION_FAILED" "Usage: spawn-can-spawn [depth] [--enforce]"
|
|
80
|
+
fi
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
done
|
|
84
|
+
[[ -z "$depth" ]] && depth=1
|
|
85
|
+
[[ "$depth" =~ ^[0-9]+$ ]] || json_err "$E_VALIDATION_FAILED" "Depth must be a non-negative integer" "{\"provided\":\"$depth\"}"
|
|
86
|
+
|
|
87
|
+
# Depth limits: 1→4 spawns, 2→2 spawns, 3+→0 spawns
|
|
88
|
+
if [[ $depth -eq 1 ]]; then
|
|
89
|
+
max_for_depth=4
|
|
90
|
+
elif [[ $depth -eq 2 ]]; then
|
|
91
|
+
max_for_depth=2
|
|
92
|
+
else
|
|
93
|
+
max_for_depth=0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Count current spawns in this session (from spawn-tree.txt)
|
|
97
|
+
current=0
|
|
98
|
+
if [[ -f "$COLONY_DATA_DIR/spawn-tree.txt" ]]; then
|
|
99
|
+
current=$(grep -c "|spawned$" "$COLONY_DATA_DIR/spawn-tree.txt" 2>/dev/null || echo 0) # SUPPRESS:OK -- read-default: count defaults to 0 if file missing
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Global cap of 10 workers per phase
|
|
103
|
+
global_cap=10
|
|
104
|
+
|
|
105
|
+
# Can spawn if: depth < 3 AND under global cap
|
|
106
|
+
if [[ $depth -lt 3 && $current -lt $global_cap ]]; then
|
|
107
|
+
can="true"
|
|
108
|
+
else
|
|
109
|
+
can="false"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [[ "$enforce_mode" == "true" && "$can" == "false" ]]; then
|
|
113
|
+
json_err "$E_VALIDATION_FAILED" "Spawn cap exceeded: depth=$depth current=$current max=$global_cap"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
json_ok "{\"can_spawn\":$can,\"depth\":$depth,\"max_spawns\":$max_for_depth,\"current_total\":$current,\"global_cap\":$global_cap}"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_spawn_get_depth() {
|
|
120
|
+
# Return depth for a given ant name by tracing spawn tree
|
|
121
|
+
# Usage: spawn-get-depth <ant_name>
|
|
122
|
+
# Queen = depth 0, Queen's spawns = depth 1, their spawns = depth 2, etc.
|
|
123
|
+
ant_name="${1:-Queen}"
|
|
124
|
+
|
|
125
|
+
if [[ "$ant_name" == "Queen" ]]; then
|
|
126
|
+
json_ok '{"ant":"Queen","depth":0}'
|
|
127
|
+
exit 0
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Check if spawn tree exists
|
|
131
|
+
if [[ ! -f "$COLONY_DATA_DIR/spawn-tree.txt" ]]; then
|
|
132
|
+
json_ok "$(jq -n --arg ant "$ant_name" '{ant: $ant, depth: 1, found: false}')"
|
|
133
|
+
exit 0
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# Check if ant exists in spawn tree (gracefully handle missing ants)
|
|
137
|
+
if ! grep -qF "|$ant_name|" "$COLONY_DATA_DIR/spawn-tree.txt" 2>/dev/null; then # SUPPRESS:OK -- existence-test: file may not exist; -F: ant_name may contain regex metacharacters
|
|
138
|
+
json_ok "$(jq -n --arg ant "$ant_name" '{ant: $ant, depth: 1, found: false}')"
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Find the spawn record for this ant and trace parents
|
|
143
|
+
depth=1
|
|
144
|
+
current_ant="$ant_name"
|
|
145
|
+
|
|
146
|
+
# Find who spawned this ant (look for lines with |spawned)
|
|
147
|
+
while true; do
|
|
148
|
+
# Format: timestamp|parent|caste|child_name|task|spawned
|
|
149
|
+
# SUPPRESS:OK -- read-default: returns fallback on failure
|
|
150
|
+
parent=$(grep -F "|$current_ant|" "$COLONY_DATA_DIR/spawn-tree.txt" 2>/dev/null | grep "|spawned$" | head -1 | cut -d'|' -f2 || echo "")
|
|
151
|
+
|
|
152
|
+
if [[ -z "$parent" || "$parent" == "Queen" ]]; then
|
|
153
|
+
break
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
depth=$((depth + 1))
|
|
157
|
+
current_ant="$parent"
|
|
158
|
+
|
|
159
|
+
# Safety limit
|
|
160
|
+
if [[ $depth -gt 5 ]]; then
|
|
161
|
+
break
|
|
162
|
+
fi
|
|
163
|
+
done
|
|
164
|
+
|
|
165
|
+
json_ok "$(jq -n --arg ant "$ant_name" --argjson depth "$depth" '{ant: $ant, depth: $depth, found: true}')"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_spawn_can_spawn_swarm() {
|
|
169
|
+
# Check if swarm can spawn more scouts (separate from phase workers)
|
|
170
|
+
# Usage: spawn-can-spawn-swarm <swarm_id>
|
|
171
|
+
# Swarm has its own cap of 6 (4 scouts + 2 sub-scouts max)
|
|
172
|
+
swarm_id="${1:-swarm}"
|
|
173
|
+
swarm_cap=6
|
|
174
|
+
|
|
175
|
+
current=0
|
|
176
|
+
if [[ -f "$COLONY_DATA_DIR/spawn-tree.txt" ]]; then
|
|
177
|
+
# SUPPRESS:OK -- existence-test: grep returns 1 when no matches
|
|
178
|
+
# -F: swarm_id may contain regex metacharacters; anchor $ dropped (swarm_id is unique, no substring collision risk)
|
|
179
|
+
current=$(grep -cF "|swarm:$swarm_id" "$COLONY_DATA_DIR/spawn-tree.txt" 2>/dev/null) || current=0
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
if [[ $current -lt $swarm_cap ]]; then
|
|
183
|
+
can="true"
|
|
184
|
+
remaining=$((swarm_cap - current))
|
|
185
|
+
else
|
|
186
|
+
can="false"
|
|
187
|
+
remaining=0
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
json_ok "$(jq -n --argjson can_spawn "$can" --argjson current "$current" \
|
|
191
|
+
--argjson cap "$swarm_cap" --argjson remaining "$remaining" --arg swarm_id "$swarm_id" \
|
|
192
|
+
'{can_spawn: $can_spawn, current: $current, cap: $cap, remaining: $remaining, swarm_id: $swarm_id}')"
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_spawn_tree_load() {
|
|
196
|
+
source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || { # SUPPRESS:OK -- read-default: utility may not be installed
|
|
197
|
+
json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
|
|
198
|
+
exit 1
|
|
199
|
+
}
|
|
200
|
+
tree_json=$(reconstruct_tree_json)
|
|
201
|
+
if echo "$tree_json" | jq -e . >/dev/null 2>&1; then
|
|
202
|
+
json_ok "$tree_json"
|
|
203
|
+
else
|
|
204
|
+
json_err "$E_VALIDATION_FAILED" "spawn tree reconstruction produced invalid JSON"
|
|
205
|
+
return 1
|
|
206
|
+
fi
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_spawn_tree_active() {
|
|
210
|
+
source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || { # SUPPRESS:OK -- read-default: utility may not be installed
|
|
211
|
+
json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
|
|
212
|
+
exit 1
|
|
213
|
+
}
|
|
214
|
+
active=$(get_active_spawns)
|
|
215
|
+
if echo "$active" | jq -e . >/dev/null 2>&1; then
|
|
216
|
+
json_ok "$active"
|
|
217
|
+
else
|
|
218
|
+
json_err "$E_VALIDATION_FAILED" "spawn-tree active produced invalid JSON"
|
|
219
|
+
return 1
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_spawn_tree_depth() {
|
|
224
|
+
ant_name="${1:-}"
|
|
225
|
+
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-tree-depth <ant_name>"
|
|
226
|
+
source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || { # SUPPRESS:OK -- read-default: utility may not be installed
|
|
227
|
+
json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
|
|
228
|
+
exit 1
|
|
229
|
+
}
|
|
230
|
+
depth=$(get_spawn_depth "$ant_name")
|
|
231
|
+
if echo "$depth" | jq -e . >/dev/null 2>&1; then
|
|
232
|
+
json_ok "$depth"
|
|
233
|
+
else
|
|
234
|
+
json_err "$E_VALIDATION_FAILED" "spawn-tree depth produced invalid JSON"
|
|
235
|
+
return 1
|
|
236
|
+
fi
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
_spawn_efficiency() {
|
|
240
|
+
# Calculate spawn efficiency metrics from spawn-tree.txt
|
|
241
|
+
# Usage: spawn-efficiency
|
|
242
|
+
spawn_tree_file="$COLONY_DATA_DIR/spawn-tree.txt"
|
|
243
|
+
total=0
|
|
244
|
+
completed=0
|
|
245
|
+
failed=0
|
|
246
|
+
|
|
247
|
+
if [[ -f "$spawn_tree_file" ]]; then
|
|
248
|
+
total=$(grep -c "|spawned$" "$spawn_tree_file" 2>/dev/null || echo 0) # SUPPRESS:OK -- read-default: count defaults to 0 if file missing
|
|
249
|
+
completed=$(grep -c "|completed$" "$spawn_tree_file" 2>/dev/null || echo 0) # SUPPRESS:OK -- read-default: count defaults to 0 if file missing
|
|
250
|
+
failed=$(grep -c "|failed$" "$spawn_tree_file" 2>/dev/null || echo 0) # SUPPRESS:OK -- read-default: count defaults to 0 if file missing
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
if [[ "$total" -gt 0 ]]; then
|
|
254
|
+
efficiency=$(( completed * 100 / total ))
|
|
255
|
+
else
|
|
256
|
+
efficiency=0
|
|
257
|
+
fi
|
|
258
|
+
|
|
259
|
+
json_ok "{\"total\":$total,\"completed\":$completed,\"failed\":$failed,\"efficiency_pct\":$efficiency}"
|
|
260
|
+
}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# State API facade -- centralized COLONY_STATE.json access
|
|
3
|
+
# Provides: _state_read, _state_write, _state_read_field, _state_mutate, _state_migrate,
|
|
4
|
+
# _colony_vital_signs
|
|
5
|
+
#
|
|
6
|
+
# These functions are sourced by aether-utils.sh at startup.
|
|
7
|
+
# All shared infrastructure (json_ok, json_err, atomic_write, acquire_lock,
|
|
8
|
+
# release_lock, LOCK_DIR, DATA_DIR, SCRIPT_DIR, error constants) is available.
|
|
9
|
+
|
|
10
|
+
_state_read() {
|
|
11
|
+
# Read full COLONY_STATE.json and return via json_ok
|
|
12
|
+
# Usage: state-read
|
|
13
|
+
# No lock needed for reads (jq is atomic on single files)
|
|
14
|
+
# Returns: json_ok with full state, or json_err on missing/invalid file
|
|
15
|
+
|
|
16
|
+
sr_state_file="$DATA_DIR/COLONY_STATE.json"
|
|
17
|
+
|
|
18
|
+
if [[ ! -f "$sr_state_file" ]]; then
|
|
19
|
+
json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
sr_content=$(cat "$sr_state_file" 2>/dev/null) || {
|
|
23
|
+
json_err "$E_FILE_NOT_FOUND" "Failed to read COLONY_STATE.json"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if ! echo "$sr_content" | jq -e . >/dev/null 2>&1; then
|
|
27
|
+
json_err "$E_JSON_INVALID" "COLONY_STATE.json contains invalid JSON"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
json_ok "$sr_content"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_state_read_field() {
|
|
34
|
+
# Read a specific jq field path from COLONY_STATE.json
|
|
35
|
+
# Usage: state-read-field <jq_path>
|
|
36
|
+
# For internal callers: outputs raw value to stdout (no json_ok wrapper)
|
|
37
|
+
# For subcommand entry: case block wraps in json_ok
|
|
38
|
+
# Returns empty string + exit 0 for missing field (callers check emptiness)
|
|
39
|
+
|
|
40
|
+
srf_field="${1:-}"
|
|
41
|
+
|
|
42
|
+
if [[ -z "$srf_field" ]]; then
|
|
43
|
+
json_err "$E_VALIDATION_FAILED" "state-read-field requires a jq field path argument"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
srf_state_file="$DATA_DIR/COLONY_STATE.json"
|
|
47
|
+
|
|
48
|
+
if [[ ! -f "$srf_state_file" ]]; then
|
|
49
|
+
json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Extract the field value (raw output, no quotes around strings)
|
|
53
|
+
srf_value=$(jq -r "$srf_field // empty" "$srf_state_file" 2>/dev/null) || srf_value=""
|
|
54
|
+
|
|
55
|
+
echo "$srf_value"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_state_write() {
|
|
59
|
+
# Write COLONY_STATE.json through a locked, validated, atomic path
|
|
60
|
+
# Usage: state-write '<json>'
|
|
61
|
+
# or: cat state.json | state-write
|
|
62
|
+
# Refactored from inline state-write case block for reuse
|
|
63
|
+
# Validates JSON, acquires lock, creates backup, writes atomically
|
|
64
|
+
|
|
65
|
+
sw_content="${1:-}"
|
|
66
|
+
if [[ -z "$sw_content" ]]; then
|
|
67
|
+
sw_content=$(cat)
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Validate JSON
|
|
71
|
+
if ! echo "$sw_content" | jq -e . >/dev/null 2>&1; then # SUPPRESS:OK -- validation: testing JSON validity
|
|
72
|
+
json_err "$E_JSON_INVALID" "state-write received invalid JSON"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
sw_state_file="$DATA_DIR/COLONY_STATE.json"
|
|
76
|
+
|
|
77
|
+
# Acquire lock (colony-level, not hub-level)
|
|
78
|
+
acquire_lock "$sw_state_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on COLONY_STATE.json"
|
|
79
|
+
|
|
80
|
+
# Create backup before writing
|
|
81
|
+
if [[ -f "$sw_state_file" ]]; then
|
|
82
|
+
if ! create_backup "$sw_state_file"; then
|
|
83
|
+
_aether_log_error "Could not create backup of colony state before writing"
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Write atomically; release lock on failure
|
|
88
|
+
atomic_write "$sw_state_file" "$sw_content" || {
|
|
89
|
+
release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
90
|
+
json_err "$E_UNKNOWN" "Failed to write COLONY_STATE.json"
|
|
91
|
+
}
|
|
92
|
+
release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
93
|
+
|
|
94
|
+
json_ok '{"written":true}'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_state_mutate() {
|
|
98
|
+
# Read-modify-write COLONY_STATE.json with a jq expression
|
|
99
|
+
# Usage: state-mutate '<jq_expression>'
|
|
100
|
+
# Acquires lock, creates backup, applies jq, validates, writes atomically
|
|
101
|
+
# Returns: json_ok with mutated:true, or json_err on failure
|
|
102
|
+
|
|
103
|
+
sm_expr="${1:-}"
|
|
104
|
+
|
|
105
|
+
if [[ -z "$sm_expr" ]]; then
|
|
106
|
+
json_err "$E_VALIDATION_FAILED" "state-mutate requires a jq expression argument"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
sm_state_file="$DATA_DIR/COLONY_STATE.json"
|
|
110
|
+
|
|
111
|
+
if [[ ! -f "$sm_state_file" ]]; then
|
|
112
|
+
json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Acquire lock for safe read-modify-write
|
|
116
|
+
acquire_lock "$sm_state_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on COLONY_STATE.json"
|
|
117
|
+
|
|
118
|
+
# Create backup before mutation
|
|
119
|
+
if type create_backup &>/dev/null; then
|
|
120
|
+
if ! create_backup "$sm_state_file"; then
|
|
121
|
+
_aether_log_error "Could not create backup of colony state before mutation"
|
|
122
|
+
fi
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# Apply jq expression to current state
|
|
126
|
+
sm_updated=$(jq "$sm_expr" "$sm_state_file" 2>/dev/null) || {
|
|
127
|
+
release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
128
|
+
json_err "$E_JSON_INVALID" "jq expression failed: $sm_expr"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Validate the result is valid JSON
|
|
132
|
+
if [[ -z "$sm_updated" ]] || ! echo "$sm_updated" | jq -e . >/dev/null 2>&1; then
|
|
133
|
+
release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
134
|
+
json_err "$E_JSON_INVALID" "state-mutate produced invalid JSON"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Write atomically
|
|
138
|
+
atomic_write "$sm_state_file" "$sm_updated" || {
|
|
139
|
+
release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
140
|
+
json_err "$E_UNKNOWN" "Failed to write mutated COLONY_STATE.json"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
144
|
+
|
|
145
|
+
json_ok '{"mutated":true}'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
_state_migrate() {
|
|
149
|
+
# Schema migration helper: auto-upgrades pre-3.0 state files to v3.0
|
|
150
|
+
# Additive only (never removes fields) -- idempotent and safe for concurrent access
|
|
151
|
+
# Moved from validate-state case block for reuse
|
|
152
|
+
|
|
153
|
+
sm_state_file="${1:-}"
|
|
154
|
+
[[ -f "$sm_state_file" ]] || return 0
|
|
155
|
+
|
|
156
|
+
# First: verify file is parseable JSON at all
|
|
157
|
+
if ! jq -e . "$sm_state_file" >/dev/null 2>&1; then # SUPPRESS:OK -- validation: testing JSON validity
|
|
158
|
+
# Corrupt state file -- backup and error
|
|
159
|
+
if type create_backup &>/dev/null; then
|
|
160
|
+
if ! create_backup "$sm_state_file"; then
|
|
161
|
+
_aether_log_error "Could not create backup of corrupted COLONY_STATE.json"
|
|
162
|
+
fi
|
|
163
|
+
fi
|
|
164
|
+
json_err "$E_JSON_INVALID" \
|
|
165
|
+
"COLONY_STATE.json is corrupted (invalid JSON). A backup was saved in .aether/data/backups/. Try: run /ant:init to reset colony state."
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
sm_current_version=$(jq -r '.version // "1.0"' "$sm_state_file" 2>/dev/null) # SUPPRESS:OK -- read-default: file may not exist yet
|
|
169
|
+
|
|
170
|
+
if [[ "$sm_current_version" != "3.0" ]]; then
|
|
171
|
+
sm_lock_held=false
|
|
172
|
+
# Skip lock acquisition when caller already holds the state lock
|
|
173
|
+
if [[ "${AETHER_STATE_LOCKED:-false}" != "true" ]] && type acquire_lock &>/dev/null; then
|
|
174
|
+
acquire_lock "$sm_state_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on COLONY_STATE.json for migration"
|
|
175
|
+
sm_lock_held=true
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# Add missing v3.0 fields (additive only -- idempotent and safe for concurrent access)
|
|
179
|
+
sm_updated=$(jq '
|
|
180
|
+
.version = "3.0" |
|
|
181
|
+
if .signals == null then .signals = [] else . end |
|
|
182
|
+
if .graveyards == null then .graveyards = [] else . end |
|
|
183
|
+
if .events == null then .events = [] else . end
|
|
184
|
+
' "$sm_state_file" 2>/dev/null) || { # SUPPRESS:OK -- read-default: file may not exist yet
|
|
185
|
+
[[ "$sm_lock_held" == "true" ]] && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
186
|
+
json_err "$E_JSON_INVALID" "Failed to migrate COLONY_STATE.json"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if [[ -n "$sm_updated" ]]; then
|
|
190
|
+
atomic_write "$sm_state_file" "$sm_updated" || {
|
|
191
|
+
[[ "$sm_lock_held" == "true" ]] && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
192
|
+
json_err "$E_JSON_INVALID" "Failed to write migrated COLONY_STATE.json"
|
|
193
|
+
}
|
|
194
|
+
# Notify user of migration (auto-migrate + notify pattern)
|
|
195
|
+
printf '{"ok":true,"warning":"W_MIGRATED","message":"Migrated colony state from v%s to v3.0"}\n' "$sm_current_version" >&2
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
[[ "$sm_lock_held" == "true" ]] && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
199
|
+
fi
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# ============================================================================
|
|
203
|
+
# _colony_vital_signs
|
|
204
|
+
# Compute colony health metrics from existing data files
|
|
205
|
+
# Usage: colony-vital-signs
|
|
206
|
+
# Returns: JSON with build_velocity, error_rate, signal_health, memory_pressure,
|
|
207
|
+
# colony_age_hours, and overall_health (0-100)
|
|
208
|
+
# Gracefully degrades: missing files produce zero/default values
|
|
209
|
+
# ============================================================================
|
|
210
|
+
_colony_vital_signs() {
|
|
211
|
+
local cvs_state_file="$COLONY_DATA_DIR/COLONY_STATE.json"
|
|
212
|
+
local cvs_midden_file="$COLONY_DATA_DIR/midden/midden.json"
|
|
213
|
+
local cvs_phero_file="$COLONY_DATA_DIR/pheromones.json"
|
|
214
|
+
local cvs_session_file="$COLONY_DATA_DIR/session.json"
|
|
215
|
+
|
|
216
|
+
# --- Compute 24h window boundary ---
|
|
217
|
+
local cvs_now
|
|
218
|
+
cvs_now=$(date -u +%s 2>/dev/null || echo "0")
|
|
219
|
+
local cvs_window_start=$(( cvs_now - 86400 ))
|
|
220
|
+
|
|
221
|
+
# ---- build_velocity: count phase_completed events in last 24h ----
|
|
222
|
+
local cvs_phases_per_day=0
|
|
223
|
+
if [[ -f "$cvs_state_file" ]]; then
|
|
224
|
+
cvs_phases_per_day=$(jq --argjson win "$cvs_window_start" '
|
|
225
|
+
[.events[]? |
|
|
226
|
+
select(. != null) |
|
|
227
|
+
select(test("\\|phase_completed\\|")) |
|
|
228
|
+
capture("^(?P<ts>[^|]+)\\|") |
|
|
229
|
+
.ts |
|
|
230
|
+
gsub("[TZ:-]"; " ") |
|
|
231
|
+
split(" ") |
|
|
232
|
+
if length >= 6 then
|
|
233
|
+
(.[0:6] | join(" ")) |
|
|
234
|
+
# convert to comparable string for ordering -- full ISO compare
|
|
235
|
+
. as $s | $s
|
|
236
|
+
else . end
|
|
237
|
+
] | length
|
|
238
|
+
' "$cvs_state_file" 2>/dev/null || echo "0")
|
|
239
|
+
|
|
240
|
+
# Simpler approach: use string comparison on ISO timestamps
|
|
241
|
+
# Compute the 24h-ago timestamp as ISO string
|
|
242
|
+
local cvs_cutoff_iso
|
|
243
|
+
cvs_cutoff_iso=$(date -u -r "$cvs_window_start" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
|
|
244
|
+
|| date -u -d "@$cvs_window_start" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
|
|
245
|
+
|| echo "")
|
|
246
|
+
|
|
247
|
+
if [[ -n "$cvs_cutoff_iso" ]]; then
|
|
248
|
+
cvs_phases_per_day=$(jq --arg cutoff "$cvs_cutoff_iso" '
|
|
249
|
+
[.events[]? |
|
|
250
|
+
select(. != null and (type == "string")) |
|
|
251
|
+
select(test("\\|phase_completed\\|")) |
|
|
252
|
+
split("|") | .[0] |
|
|
253
|
+
select(. >= $cutoff)
|
|
254
|
+
] | length
|
|
255
|
+
' "$cvs_state_file" 2>/dev/null || echo "0")
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
# Normalize: ensure integer
|
|
259
|
+
cvs_phases_per_day=$(( cvs_phases_per_day + 0 )) 2>/dev/null || cvs_phases_per_day=0
|
|
260
|
+
|
|
261
|
+
# Determine trend (simple heuristic: any builds = steady, 0 = idle)
|
|
262
|
+
local cvs_bv_trend="idle"
|
|
263
|
+
[[ "$cvs_phases_per_day" -ge 1 ]] && cvs_bv_trend="steady"
|
|
264
|
+
[[ "$cvs_phases_per_day" -ge 3 ]] && cvs_bv_trend="accelerating"
|
|
265
|
+
|
|
266
|
+
# ---- error_rate: unreviewed midden entries in last 24h ----
|
|
267
|
+
local cvs_errors_per_day=0
|
|
268
|
+
local cvs_err_status="clean"
|
|
269
|
+
if [[ -f "$cvs_midden_file" ]]; then
|
|
270
|
+
local cvs_cutoff_iso_err
|
|
271
|
+
cvs_cutoff_iso_err=$(date -u -r "$cvs_window_start" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
|
|
272
|
+
|| date -u -d "@$cvs_window_start" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
|
|
273
|
+
|| echo "")
|
|
274
|
+
|
|
275
|
+
if [[ -n "$cvs_cutoff_iso_err" ]]; then
|
|
276
|
+
cvs_errors_per_day=$(jq --arg cutoff "$cvs_cutoff_iso_err" '
|
|
277
|
+
[(.entries // [])[]? |
|
|
278
|
+
select(.reviewed == false or .reviewed == null) |
|
|
279
|
+
select((.timestamp // "") >= $cutoff)
|
|
280
|
+
] | length
|
|
281
|
+
' "$cvs_midden_file" 2>/dev/null || echo "0")
|
|
282
|
+
else
|
|
283
|
+
# Fallback: count all unreviewed
|
|
284
|
+
cvs_errors_per_day=$(jq '
|
|
285
|
+
[(.entries // [])[]? | select(.reviewed == false or .reviewed == null)] | length
|
|
286
|
+
' "$cvs_midden_file" 2>/dev/null || echo "0")
|
|
287
|
+
fi
|
|
288
|
+
fi
|
|
289
|
+
cvs_errors_per_day=$(( cvs_errors_per_day + 0 )) 2>/dev/null || cvs_errors_per_day=0
|
|
290
|
+
|
|
291
|
+
if [[ "$cvs_errors_per_day" -eq 0 ]]; then
|
|
292
|
+
cvs_err_status="clean"
|
|
293
|
+
elif [[ "$cvs_errors_per_day" -le 2 ]]; then
|
|
294
|
+
cvs_err_status="nominal"
|
|
295
|
+
elif [[ "$cvs_errors_per_day" -le 5 ]]; then
|
|
296
|
+
cvs_err_status="elevated"
|
|
297
|
+
else
|
|
298
|
+
cvs_err_status="critical"
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# ---- signal_health: count active pheromones ----
|
|
302
|
+
local cvs_active_count=0
|
|
303
|
+
local cvs_sig_status="dormant"
|
|
304
|
+
if [[ -f "$cvs_phero_file" ]]; then
|
|
305
|
+
cvs_active_count=$(jq '
|
|
306
|
+
[.signals[]? | select(.active == true)] | length
|
|
307
|
+
' "$cvs_phero_file" 2>/dev/null || echo "0")
|
|
308
|
+
fi
|
|
309
|
+
cvs_active_count=$(( cvs_active_count + 0 )) 2>/dev/null || cvs_active_count=0
|
|
310
|
+
|
|
311
|
+
if [[ "$cvs_active_count" -eq 0 ]]; then
|
|
312
|
+
cvs_sig_status="dormant"
|
|
313
|
+
elif [[ "$cvs_active_count" -le 3 ]]; then
|
|
314
|
+
cvs_sig_status="guided"
|
|
315
|
+
else
|
|
316
|
+
cvs_sig_status="active"
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
# ---- memory_pressure: count instincts ----
|
|
320
|
+
local cvs_instinct_count=0
|
|
321
|
+
local cvs_mem_status="empty"
|
|
322
|
+
if [[ -f "$cvs_state_file" ]]; then
|
|
323
|
+
# instincts may be a JSON string (serialized array) or a real array
|
|
324
|
+
local cvs_raw_instincts
|
|
325
|
+
cvs_raw_instincts=$(jq -r '.memory.instincts // "[]"' "$cvs_state_file" 2>/dev/null || echo "[]")
|
|
326
|
+
# Handle both string-encoded and native array
|
|
327
|
+
cvs_instinct_count=$(echo "$cvs_raw_instincts" | jq -r 'if type == "string" then (. | fromjson | length) elif type == "array" then length else 0 end' 2>/dev/null || echo "0")
|
|
328
|
+
fi
|
|
329
|
+
cvs_instinct_count=$(( cvs_instinct_count + 0 )) 2>/dev/null || cvs_instinct_count=0
|
|
330
|
+
|
|
331
|
+
if [[ "$cvs_instinct_count" -eq 0 ]]; then
|
|
332
|
+
cvs_mem_status="empty"
|
|
333
|
+
elif [[ "$cvs_instinct_count" -le 5 ]]; then
|
|
334
|
+
cvs_mem_status="growing"
|
|
335
|
+
elif [[ "$cvs_instinct_count" -le 15 ]]; then
|
|
336
|
+
cvs_mem_status="healthy"
|
|
337
|
+
else
|
|
338
|
+
cvs_mem_status="rich"
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
# ---- colony_age_hours: hours since initialized_at ----
|
|
342
|
+
local cvs_age_hours=0
|
|
343
|
+
if [[ -f "$cvs_state_file" ]]; then
|
|
344
|
+
local cvs_init_at
|
|
345
|
+
cvs_init_at=$(jq -r '.initialized_at // empty' "$cvs_state_file" 2>/dev/null || echo "")
|
|
346
|
+
if [[ -n "$cvs_init_at" ]]; then
|
|
347
|
+
local cvs_init_ts
|
|
348
|
+
cvs_init_ts=$(date -u -j -f '%Y-%m-%dT%H:%M:%SZ' "$cvs_init_at" '+%s' 2>/dev/null \
|
|
349
|
+
|| date -u -d "$cvs_init_at" '+%s' 2>/dev/null \
|
|
350
|
+
|| echo "0")
|
|
351
|
+
if [[ "$cvs_init_ts" -gt 0 && "$cvs_now" -gt "$cvs_init_ts" ]]; then
|
|
352
|
+
cvs_age_hours=$(( (cvs_now - cvs_init_ts) / 3600 ))
|
|
353
|
+
fi
|
|
354
|
+
fi
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
# ---- overall_health: weighted 0-100 score ----
|
|
358
|
+
# Components (max points each):
|
|
359
|
+
# recent builds (+30): has at least one phase_completed in 24h
|
|
360
|
+
# low errors (+30): zero unreviewed errors in 24h
|
|
361
|
+
# signals exist (+20): at least one active pheromone
|
|
362
|
+
# instincts growing (+20): at least one instinct
|
|
363
|
+
local cvs_score=0
|
|
364
|
+
[[ "$cvs_phases_per_day" -ge 1 ]] && cvs_score=$(( cvs_score + 30 ))
|
|
365
|
+
[[ "$cvs_errors_per_day" -eq 0 ]] && cvs_score=$(( cvs_score + 30 ))
|
|
366
|
+
[[ "$cvs_active_count" -ge 1 ]] && cvs_score=$(( cvs_score + 20 ))
|
|
367
|
+
[[ "$cvs_instinct_count" -ge 1 ]] && cvs_score=$(( cvs_score + 20 ))
|
|
368
|
+
[[ "$cvs_score" -gt 100 ]] && cvs_score=100
|
|
369
|
+
|
|
370
|
+
json_ok "$(jq -n \
|
|
371
|
+
--argjson phases_per_day "$cvs_phases_per_day" \
|
|
372
|
+
--arg bv_trend "$cvs_bv_trend" \
|
|
373
|
+
--argjson errors_per_day "$cvs_errors_per_day" \
|
|
374
|
+
--arg err_status "$cvs_err_status" \
|
|
375
|
+
--argjson active_count "$cvs_active_count" \
|
|
376
|
+
--arg sig_status "$cvs_sig_status" \
|
|
377
|
+
--argjson instinct_count "$cvs_instinct_count" \
|
|
378
|
+
--arg mem_status "$cvs_mem_status" \
|
|
379
|
+
--argjson age_hours "$cvs_age_hours" \
|
|
380
|
+
--argjson overall_health "$cvs_score" \
|
|
381
|
+
'{
|
|
382
|
+
build_velocity: {phases_per_day: $phases_per_day, trend: $bv_trend},
|
|
383
|
+
error_rate: {errors_per_day: $errors_per_day, status: $err_status},
|
|
384
|
+
signal_health: {active_count: $active_count, status: $sig_status},
|
|
385
|
+
memory_pressure: {instinct_count: $instinct_count, status: $mem_status},
|
|
386
|
+
colony_age_hours: $age_hours,
|
|
387
|
+
overall_health: $overall_health
|
|
388
|
+
}')"
|
|
389
|
+
}
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
#
|
|
11
11
|
# Provides: load_colony_state, unload_colony_state, get_handoff_summary, display_resumption_context
|
|
12
12
|
|
|
13
|
-
# Aether root detection - use git root
|
|
14
|
-
if
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
# Aether root detection - respect existing AETHER_ROOT, or use git root, or use current directory
|
|
14
|
+
if [[ -z "${AETHER_ROOT:-}" ]]; then
|
|
15
|
+
if git rev-parse --show-toplevel >/dev/null 2>&1; then
|
|
16
|
+
AETHER_ROOT="$(git rev-parse --show-toplevel)"
|
|
17
|
+
else
|
|
18
|
+
AETHER_ROOT="$(pwd)"
|
|
19
|
+
fi
|
|
18
20
|
fi
|
|
19
21
|
|
|
20
22
|
SCRIPT_DIR="${SCRIPT_DIR:-$AETHER_ROOT/.aether}"
|
|
@@ -70,7 +72,7 @@ load_colony_state() {
|
|
|
70
72
|
|
|
71
73
|
# Validate state before loading
|
|
72
74
|
local validation
|
|
73
|
-
validation=$(bash "$SCRIPT_DIR/aether-utils.sh" validate-state colony 2>/dev/null)
|
|
75
|
+
validation=$(AETHER_STATE_LOCKED=true bash "$SCRIPT_DIR/aether-utils.sh" validate-state colony 2>/dev/null)
|
|
74
76
|
if ! echo "$validation" | jq -e '.result.pass' >/dev/null 2>&1; then
|
|
75
77
|
# Validation failed - release lock and report error
|
|
76
78
|
if type release_lock &>/dev/null; then
|