aether-colony 5.1.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 +122 -42
- package/.aether/commands/colonize.yaml +4 -0
- package/.aether/commands/council.yaml +205 -0
- package/.aether/commands/init.yaml +46 -13
- package/.aether/commands/insert-phase.yaml +4 -0
- package/.aether/commands/plan.yaml +53 -2
- package/.aether/commands/quick.yaml +104 -0
- package/.aether/commands/resume-colony.yaml +6 -4
- package/.aether/commands/resume.yaml +9 -0
- package/.aether/commands/run.yaml +37 -1
- package/.aether/commands/seal.yaml +9 -0
- package/.aether/commands/status.yaml +45 -1
- package/.aether/docs/command-playbooks/build-full.md +2 -1
- package/.aether/docs/command-playbooks/build-prep.md +2 -1
- package/.aether/docs/command-playbooks/continue-full.md +1 -0
- package/.aether/docs/command-playbooks/continue-verify.md +1 -0
- package/.aether/utils/council.sh +425 -0
- package/.aether/utils/error-handler.sh +3 -3
- package/.aether/utils/flag.sh +23 -12
- package/.aether/utils/hive.sh +2 -2
- package/.aether/utils/immune.sh +508 -0
- package/.aether/utils/learning.sh +2 -2
- package/.aether/utils/midden.sh +178 -0
- package/.aether/utils/queen.sh +29 -17
- package/.aether/utils/session.sh +264 -0
- package/.aether/utils/spawn-tree.sh +7 -7
- package/.aether/utils/spawn.sh +2 -2
- package/.aether/utils/state-api.sh +191 -1
- package/.claude/commands/ant/colonize.md +2 -0
- package/.claude/commands/ant/council.md +205 -0
- package/.claude/commands/ant/init.md +46 -13
- package/.claude/commands/ant/insert-phase.md +4 -0
- package/.claude/commands/ant/plan.md +27 -1
- package/.claude/commands/ant/quick.md +100 -0
- package/.claude/commands/ant/resume-colony.md +3 -2
- package/.claude/commands/ant/resume.md +9 -0
- package/.claude/commands/ant/run.md +37 -1
- package/.claude/commands/ant/seal.md +9 -0
- package/.claude/commands/ant/status.md +45 -1
- package/.opencode/commands/ant/colonize.md +2 -0
- package/.opencode/commands/ant/council.md +143 -0
- package/.opencode/commands/ant/init.md +46 -13
- package/.opencode/commands/ant/insert-phase.md +4 -0
- package/.opencode/commands/ant/plan.md +26 -1
- package/.opencode/commands/ant/quick.md +91 -0
- package/.opencode/commands/ant/resume-colony.md +3 -2
- package/.opencode/commands/ant/resume.md +9 -0
- package/.opencode/commands/ant/run.md +37 -1
- package/.opencode/commands/ant/status.md +2 -0
- package/CHANGELOG.md +90 -0
- package/README.md +23 -0
- package/package.json +10 -2
package/.aether/utils/queen.sh
CHANGED
|
@@ -488,8 +488,9 @@ _queen_promote() {
|
|
|
488
488
|
ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
|
|
489
489
|
|
|
490
490
|
# Use awk for cross-platform insertion (only if separator found)
|
|
491
|
+
# Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
|
|
491
492
|
if [[ -n "$ev_separator" ]]; then
|
|
492
|
-
|
|
493
|
+
AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
|
|
493
494
|
fi
|
|
494
495
|
|
|
495
496
|
# Update METADATA stats in temp file
|
|
@@ -528,9 +529,10 @@ _queen_promote() {
|
|
|
528
529
|
ev_log_entry="{\"timestamp\": \"$ts\", \"action\": \"promote\", \"wisdom_type\": \"$wisdom_type\", \"content_hash\": \"$content_hash\", \"colony\": \"$colony_name\"}"
|
|
529
530
|
|
|
530
531
|
# Check if evolution_log exists in metadata, add if not
|
|
532
|
+
# Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
|
|
531
533
|
if ! grep -q '"evolution_log"' "$tmp_file"; then
|
|
532
534
|
# Add evolution_log array after stats
|
|
533
|
-
|
|
535
|
+
AETHER_EV_LOG_ENTRY="$ev_log_entry" awk '
|
|
534
536
|
/"stats": \{/ {
|
|
535
537
|
print
|
|
536
538
|
# Read until closing brace of stats
|
|
@@ -540,19 +542,19 @@ _queen_promote() {
|
|
|
540
542
|
}
|
|
541
543
|
# Add comma and evolution_log
|
|
542
544
|
print ","
|
|
543
|
-
print " \"evolution_log\": ["
|
|
545
|
+
print " \"evolution_log\": [" ENVIRON["AETHER_EV_LOG_ENTRY"] "]"
|
|
544
546
|
next
|
|
545
547
|
}
|
|
546
548
|
{ print }
|
|
547
549
|
' "$tmp_file" > "${tmp_file}.evlog" && mv "${tmp_file}.evlog" "$tmp_file"
|
|
548
550
|
else
|
|
549
551
|
# Append to existing evolution_log array
|
|
550
|
-
|
|
552
|
+
AETHER_EV_LOG_ENTRY="$ev_log_entry" awk '
|
|
551
553
|
/"evolution_log": \[/ {
|
|
552
554
|
# Check if array is empty or has items
|
|
553
555
|
if (/\]/) {
|
|
554
556
|
# Empty array - replace with entry
|
|
555
|
-
gsub(/"evolution_log": \[\]/, "\"evolution_log\": ["
|
|
557
|
+
gsub(/"evolution_log": \[\]/, "\"evolution_log\": [" ENVIRON["AETHER_EV_LOG_ENTRY"] "]")
|
|
556
558
|
} else {
|
|
557
559
|
# Has items - need to add before closing bracket
|
|
558
560
|
# For now, just print and handle in next iteration
|
|
@@ -566,7 +568,7 @@ _queen_promote() {
|
|
|
566
568
|
getline
|
|
567
569
|
if (/\]/) {
|
|
568
570
|
# Was empty, now add entry
|
|
569
|
-
print
|
|
571
|
+
print ENVIRON["AETHER_EV_LOG_ENTRY"]
|
|
570
572
|
print "]"
|
|
571
573
|
} else {
|
|
572
574
|
# Has items, add comma and entry before closing
|
|
@@ -574,7 +576,7 @@ _queen_promote() {
|
|
|
574
576
|
while (getline > 0) {
|
|
575
577
|
if (/^\s*\]/) {
|
|
576
578
|
print ","
|
|
577
|
-
print
|
|
579
|
+
print ENVIRON["AETHER_EV_LOG_ENTRY"]
|
|
578
580
|
print "]"
|
|
579
581
|
break
|
|
580
582
|
}
|
|
@@ -628,12 +630,18 @@ _queen_promote() {
|
|
|
628
630
|
if [[ -n "$meta_section" ]]; then
|
|
629
631
|
# SUPPRESS:OK -- read-default: returns fallback on failure
|
|
630
632
|
updated_meta=$(echo "$meta_section" | jq --arg hash "$content_hash" --argjson cols "$colonies_json" '.colonies_contributed[$hash] = $cols' 2>/dev/null || echo "$meta_section")
|
|
631
|
-
# Replace metadata section
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
$
|
|
635
|
-
|
|
636
|
-
|
|
633
|
+
# Replace metadata section using head/tail to handle multi-line content safely
|
|
634
|
+
# awk -v cannot handle embedded newlines in variable values (C-escape interpretation)
|
|
635
|
+
local meta_start_line meta_end_line
|
|
636
|
+
meta_start_line=$(grep -n "^<!-- METADATA$" "$tmp_file" | head -1 | cut -d: -f1)
|
|
637
|
+
meta_end_line=$(grep -n "^-->$" "$tmp_file" | head -1 | cut -d: -f1)
|
|
638
|
+
if [[ -n "$meta_start_line" && -n "$meta_end_line" ]]; then
|
|
639
|
+
{
|
|
640
|
+
head -n $((meta_start_line - 1)) "$tmp_file"
|
|
641
|
+
printf '<!-- METADATA\n%s\n-->\n' "$updated_meta"
|
|
642
|
+
tail -n +$((meta_end_line + 1)) "$tmp_file"
|
|
643
|
+
} > "${tmp_file}.metaupd" && mv "${tmp_file}.metaupd" "$tmp_file"
|
|
644
|
+
fi
|
|
637
645
|
fi
|
|
638
646
|
fi
|
|
639
647
|
|
|
@@ -837,7 +845,8 @@ _queen_write_learnings() {
|
|
|
837
845
|
local ev_separator
|
|
838
846
|
ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
|
|
839
847
|
if [[ -n "$ev_separator" ]]; then
|
|
840
|
-
|
|
848
|
+
# Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
|
|
849
|
+
AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
|
|
841
850
|
fi
|
|
842
851
|
|
|
843
852
|
# Update METADATA stats: increment total_build_learnings
|
|
@@ -967,7 +976,8 @@ _queen_promote_instinct() {
|
|
|
967
976
|
local ev_separator
|
|
968
977
|
ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
|
|
969
978
|
if [[ -n "$ev_separator" ]]; then
|
|
970
|
-
|
|
979
|
+
# Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
|
|
980
|
+
AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
|
|
971
981
|
fi
|
|
972
982
|
|
|
973
983
|
# Update METADATA stats: increment total_instincts
|
|
@@ -1110,7 +1120,8 @@ _queen_seed_from_hive() {
|
|
|
1110
1120
|
local ev_separator
|
|
1111
1121
|
ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
|
|
1112
1122
|
if [[ -n "$ev_separator" ]]; then
|
|
1113
|
-
|
|
1123
|
+
# Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
|
|
1124
|
+
AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
|
|
1114
1125
|
fi
|
|
1115
1126
|
|
|
1116
1127
|
# Update METADATA stats: increment total_codebase_patterns
|
|
@@ -1601,7 +1612,8 @@ _queen_write_charter() {
|
|
|
1601
1612
|
local ev_separator
|
|
1602
1613
|
ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
|
|
1603
1614
|
if [[ -n "$ev_separator" ]]; then
|
|
1604
|
-
|
|
1615
|
+
# Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
|
|
1616
|
+
AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
|
|
1605
1617
|
fi
|
|
1606
1618
|
|
|
1607
1619
|
# Update METADATA stats -- count non-charter list items in each section, add charter entries
|
package/.aether/utils/session.sh
CHANGED
|
@@ -550,3 +550,267 @@ _session_summary() {
|
|
|
550
550
|
[[ "$cleared" == "true" ]] && echo "Status: Context was cleared"
|
|
551
551
|
fi
|
|
552
552
|
}
|
|
553
|
+
|
|
554
|
+
# ============================================================================
|
|
555
|
+
# _pending_decision_add
|
|
556
|
+
# Add a decision to the pending decisions queue
|
|
557
|
+
# Usage: pending-decision-add --type <type> --description <desc> [--phase N] [--source <src>]
|
|
558
|
+
# Types: visual_checkpoint, replan, escalation, runtime_verification, user_input
|
|
559
|
+
# ============================================================================
|
|
560
|
+
_pending_decision_add() {
|
|
561
|
+
local pd_type=""
|
|
562
|
+
local pd_description=""
|
|
563
|
+
local pd_phase="null"
|
|
564
|
+
local pd_source=""
|
|
565
|
+
|
|
566
|
+
while [[ $# -gt 0 ]]; do
|
|
567
|
+
case "$1" in
|
|
568
|
+
--type) pd_type="$2"; shift 2 ;;
|
|
569
|
+
--description) pd_description="$2"; shift 2 ;;
|
|
570
|
+
--phase) pd_phase="$2"; shift 2 ;;
|
|
571
|
+
--source) pd_source="$2"; shift 2 ;;
|
|
572
|
+
*) shift ;;
|
|
573
|
+
esac
|
|
574
|
+
done
|
|
575
|
+
|
|
576
|
+
[[ -z "$pd_type" ]] && json_err "$E_VALIDATION_FAILED" "pending-decision-add requires --type"
|
|
577
|
+
[[ -z "$pd_description" ]] && json_err "$E_VALIDATION_FAILED" "pending-decision-add requires --description"
|
|
578
|
+
|
|
579
|
+
local pd_file="$COLONY_DATA_DIR/pending-decisions.json"
|
|
580
|
+
local pd_id="pd_$(date +%s)_$$"
|
|
581
|
+
local pd_now
|
|
582
|
+
pd_now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
583
|
+
|
|
584
|
+
# Acquire lock for concurrent access
|
|
585
|
+
if type acquire_lock &>/dev/null; then
|
|
586
|
+
acquire_lock "$pd_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on pending-decisions.json"
|
|
587
|
+
trap 'release_lock 2>/dev/null || true' EXIT # SUPPRESS:OK -- cleanup: lock may not be held
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
# Initialize file if missing
|
|
591
|
+
if [[ ! -f "$pd_file" ]]; then
|
|
592
|
+
echo '{"version":"1.0","decisions":[]}' > "$pd_file"
|
|
593
|
+
fi
|
|
594
|
+
|
|
595
|
+
local pd_current
|
|
596
|
+
pd_current=$(cat "$pd_file" 2>/dev/null || echo '{"version":"1.0","decisions":[]}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
597
|
+
|
|
598
|
+
# Build new decision entry
|
|
599
|
+
local pd_phase_val
|
|
600
|
+
if [[ "$pd_phase" == "null" ]]; then
|
|
601
|
+
pd_phase_val="null"
|
|
602
|
+
else
|
|
603
|
+
pd_phase_val="$pd_phase"
|
|
604
|
+
fi
|
|
605
|
+
|
|
606
|
+
local pd_updated
|
|
607
|
+
pd_updated=$(echo "$pd_current" | jq \
|
|
608
|
+
--arg id "$pd_id" \
|
|
609
|
+
--arg type "$pd_type" \
|
|
610
|
+
--arg description "$pd_description" \
|
|
611
|
+
--argjson phase "${pd_phase_val}" \
|
|
612
|
+
--arg source "$pd_source" \
|
|
613
|
+
--arg created_at "$pd_now" \
|
|
614
|
+
'.decisions += [{
|
|
615
|
+
id: $id,
|
|
616
|
+
type: $type,
|
|
617
|
+
description: $description,
|
|
618
|
+
phase: $phase,
|
|
619
|
+
source: $source,
|
|
620
|
+
created_at: $created_at,
|
|
621
|
+
resolved: false
|
|
622
|
+
}]' 2>/dev/null) || {
|
|
623
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
624
|
+
json_err "$E_JSON_INVALID" "Failed to append decision to pending-decisions.json"
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
atomic_write "$pd_file" "$pd_updated" || {
|
|
628
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
629
|
+
json_err "$E_JSON_INVALID" "Failed to write pending-decisions.json"
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
type release_lock &>/dev/null && { release_lock 2>/dev/null || true; trap - EXIT; } # SUPPRESS:OK -- cleanup: lock may not be held
|
|
633
|
+
|
|
634
|
+
local pd_count
|
|
635
|
+
pd_count=$(echo "$pd_updated" | jq '.decisions | length')
|
|
636
|
+
|
|
637
|
+
json_ok "$(jq -n --arg id "$pd_id" --argjson count "$pd_count" \
|
|
638
|
+
'{id: $id, decision_count: $count}')"
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
# ============================================================================
|
|
642
|
+
# _pending_decision_list
|
|
643
|
+
# List decisions from the pending decisions queue
|
|
644
|
+
# Usage: pending-decision-list [--unresolved] [--type <type>]
|
|
645
|
+
# Default: show only unresolved
|
|
646
|
+
# ============================================================================
|
|
647
|
+
_pending_decision_list() {
|
|
648
|
+
local pd_unresolved_only="true"
|
|
649
|
+
local pd_filter_type=""
|
|
650
|
+
|
|
651
|
+
while [[ $# -gt 0 ]]; do
|
|
652
|
+
case "$1" in
|
|
653
|
+
--unresolved) pd_unresolved_only="true"; shift ;;
|
|
654
|
+
--type) pd_filter_type="$2"; shift 2 ;;
|
|
655
|
+
*) shift ;;
|
|
656
|
+
esac
|
|
657
|
+
done
|
|
658
|
+
|
|
659
|
+
local pd_file="$COLONY_DATA_DIR/pending-decisions.json"
|
|
660
|
+
|
|
661
|
+
if [[ ! -f "$pd_file" ]]; then
|
|
662
|
+
json_ok '{"total":0,"unresolved":0,"decisions":[]}'
|
|
663
|
+
exit 0
|
|
664
|
+
fi
|
|
665
|
+
|
|
666
|
+
local pd_data
|
|
667
|
+
pd_data=$(cat "$pd_file" 2>/dev/null || echo '{"version":"1.0","decisions":[]}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
668
|
+
|
|
669
|
+
# Build jq filter
|
|
670
|
+
local pd_filter='.decisions'
|
|
671
|
+
|
|
672
|
+
# Apply type filter if provided
|
|
673
|
+
if [[ -n "$pd_filter_type" ]]; then
|
|
674
|
+
pd_filter="$pd_filter | map(select(.type == \"$pd_filter_type\"))"
|
|
675
|
+
fi
|
|
676
|
+
|
|
677
|
+
# Apply resolved filter (default: only unresolved)
|
|
678
|
+
if [[ "$pd_unresolved_only" == "true" ]]; then
|
|
679
|
+
pd_filter="$pd_filter | map(select(.resolved == false))"
|
|
680
|
+
fi
|
|
681
|
+
|
|
682
|
+
local pd_total pd_unresolved pd_decisions
|
|
683
|
+
pd_total=$(echo "$pd_data" | jq '.decisions | length')
|
|
684
|
+
pd_unresolved=$(echo "$pd_data" | jq '[.decisions[] | select(.resolved == false)] | length')
|
|
685
|
+
pd_decisions=$(echo "$pd_data" | jq "$pd_filter")
|
|
686
|
+
|
|
687
|
+
json_ok "$(jq -n --argjson total "$pd_total" --argjson unresolved "$pd_unresolved" \
|
|
688
|
+
--argjson decisions "$pd_decisions" \
|
|
689
|
+
'{total: $total, unresolved: $unresolved, decisions: $decisions}')"
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
# ============================================================================
|
|
693
|
+
# _pending_decision_resolve
|
|
694
|
+
# Mark a pending decision as resolved
|
|
695
|
+
# Usage: pending-decision-resolve --id <id> --resolution <text>
|
|
696
|
+
# ============================================================================
|
|
697
|
+
_pending_decision_resolve() {
|
|
698
|
+
local pd_id=""
|
|
699
|
+
local pd_resolution=""
|
|
700
|
+
|
|
701
|
+
while [[ $# -gt 0 ]]; do
|
|
702
|
+
case "$1" in
|
|
703
|
+
--id) pd_id="$2"; shift 2 ;;
|
|
704
|
+
--resolution) pd_resolution="$2"; shift 2 ;;
|
|
705
|
+
*) shift ;;
|
|
706
|
+
esac
|
|
707
|
+
done
|
|
708
|
+
|
|
709
|
+
[[ -z "$pd_id" ]] && json_err "$E_VALIDATION_FAILED" "pending-decision-resolve requires --id"
|
|
710
|
+
[[ -z "$pd_resolution" ]] && json_err "$E_VALIDATION_FAILED" "pending-decision-resolve requires --resolution"
|
|
711
|
+
|
|
712
|
+
local pd_file="$COLONY_DATA_DIR/pending-decisions.json"
|
|
713
|
+
|
|
714
|
+
if [[ ! -f "$pd_file" ]]; then
|
|
715
|
+
json_err "$E_RESOURCE_NOT_FOUND" "No pending decisions file found"
|
|
716
|
+
fi
|
|
717
|
+
|
|
718
|
+
# Acquire lock for concurrent access
|
|
719
|
+
if type acquire_lock &>/dev/null; then
|
|
720
|
+
acquire_lock "$pd_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on pending-decisions.json"
|
|
721
|
+
trap 'release_lock 2>/dev/null || true' EXIT # SUPPRESS:OK -- cleanup: lock may not be held
|
|
722
|
+
fi
|
|
723
|
+
|
|
724
|
+
local pd_data
|
|
725
|
+
pd_data=$(cat "$pd_file" 2>/dev/null || echo '{"version":"1.0","decisions":[]}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
726
|
+
|
|
727
|
+
# Check if ID exists
|
|
728
|
+
local pd_exists
|
|
729
|
+
pd_exists=$(echo "$pd_data" | jq --arg id "$pd_id" '[.decisions[] | select(.id == $id)] | length')
|
|
730
|
+
if [[ "$pd_exists" -eq 0 ]]; then
|
|
731
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
732
|
+
json_err "$E_RESOURCE_NOT_FOUND" "Decision not found: $pd_id"
|
|
733
|
+
fi
|
|
734
|
+
|
|
735
|
+
local pd_now
|
|
736
|
+
pd_now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
737
|
+
|
|
738
|
+
local pd_updated
|
|
739
|
+
pd_updated=$(echo "$pd_data" | jq \
|
|
740
|
+
--arg id "$pd_id" \
|
|
741
|
+
--arg resolution "$pd_resolution" \
|
|
742
|
+
--arg resolved_at "$pd_now" \
|
|
743
|
+
'(.decisions[] | select(.id == $id)) |= (. + {resolved: true, resolution: $resolution, resolved_at: $resolved_at})' 2>/dev/null) || {
|
|
744
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
745
|
+
json_err "$E_JSON_INVALID" "Failed to resolve decision in pending-decisions.json"
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
atomic_write "$pd_file" "$pd_updated" || {
|
|
749
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
750
|
+
json_err "$E_JSON_INVALID" "Failed to write pending-decisions.json"
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
type release_lock &>/dev/null && { release_lock 2>/dev/null || true; trap - EXIT; } # SUPPRESS:OK -- cleanup: lock may not be held
|
|
754
|
+
|
|
755
|
+
json_ok "$(jq -n --arg id "$pd_id" '{resolved: true, id: $id}')"
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
# ============================================================================
|
|
759
|
+
# _autopilot_headless_check
|
|
760
|
+
# Check whether headless mode is active in run-state.json
|
|
761
|
+
# Usage: autopilot-headless-check
|
|
762
|
+
# Returns: {"ok":true,"result":{"headless":true|false}}
|
|
763
|
+
# ============================================================================
|
|
764
|
+
_autopilot_headless_check() {
|
|
765
|
+
local ah_state_file="$COLONY_DATA_DIR/run-state.json"
|
|
766
|
+
|
|
767
|
+
if [[ ! -f "$ah_state_file" ]]; then
|
|
768
|
+
json_ok '{"headless":false}'
|
|
769
|
+
exit 0
|
|
770
|
+
fi
|
|
771
|
+
|
|
772
|
+
local ah_headless
|
|
773
|
+
ah_headless=$(jq -r '.headless // false' "$ah_state_file" 2>/dev/null || echo "false") # SUPPRESS:OK -- read-default: field may not exist
|
|
774
|
+
|
|
775
|
+
# Normalize to boolean
|
|
776
|
+
if [[ "$ah_headless" == "true" ]]; then
|
|
777
|
+
json_ok '{"headless":true}'
|
|
778
|
+
else
|
|
779
|
+
json_ok '{"headless":false}'
|
|
780
|
+
fi
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
# ============================================================================
|
|
784
|
+
# _autopilot_set_headless
|
|
785
|
+
# Set the headless flag in run-state.json
|
|
786
|
+
# Usage: autopilot-set-headless <true|false>
|
|
787
|
+
# Returns: {"ok":true,"result":{"headless":true|false,"updated":true}}
|
|
788
|
+
# ============================================================================
|
|
789
|
+
_autopilot_set_headless() {
|
|
790
|
+
local ah_value="${1:-}"
|
|
791
|
+
|
|
792
|
+
if [[ "$ah_value" != "true" && "$ah_value" != "false" ]]; then
|
|
793
|
+
json_err "$E_VALIDATION_FAILED" "autopilot-set-headless requires true or false argument"
|
|
794
|
+
fi
|
|
795
|
+
|
|
796
|
+
local ah_state_file="$COLONY_DATA_DIR/run-state.json"
|
|
797
|
+
|
|
798
|
+
if [[ ! -f "$ah_state_file" ]]; then
|
|
799
|
+
json_err "$E_FILE_NOT_FOUND" "run-state.json not found — autopilot not active"
|
|
800
|
+
fi
|
|
801
|
+
|
|
802
|
+
local ah_headless_bool
|
|
803
|
+
[[ "$ah_value" == "true" ]] && ah_headless_bool=true || ah_headless_bool=false
|
|
804
|
+
|
|
805
|
+
local ah_current ah_updated
|
|
806
|
+
ah_current=$(cat "$ah_state_file" 2>/dev/null || echo '{}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
807
|
+
ah_updated=$(echo "$ah_current" | jq --argjson headless "$ah_headless_bool" '.headless = $headless' 2>/dev/null) || {
|
|
808
|
+
json_err "$E_JSON_INVALID" "Failed to update headless flag in run-state.json"
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
atomic_write "$ah_state_file" "$ah_updated" || {
|
|
812
|
+
json_err "$E_JSON_INVALID" "Failed to write run-state.json"
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
json_ok "$(jq -n --argjson headless "$ah_headless_bool" '{headless: $headless, updated: true}')"
|
|
816
|
+
}
|
|
@@ -51,9 +51,9 @@ parse_spawn_tree() {
|
|
|
51
51
|
printf "\"spawns\":["
|
|
52
52
|
for (i = 0; i < n; i++) {
|
|
53
53
|
if (i > 0) printf ","
|
|
54
|
-
nm = names[i]; gsub(/\\/, "\\\\", nm); gsub(/"/, "\\\"", nm); gsub(/\t/, "\\t", nm)
|
|
55
|
-
pr = parents[i]; gsub(/\\/, "\\\\", pr); gsub(/"/, "\\\"", pr); gsub(/\t/, "\\t", pr)
|
|
56
|
-
tk = tasks[i]; gsub(/\\/, "\\\\", tk); gsub(/"/, "\\\"", tk); gsub(/\t/, "\\t", tk)
|
|
54
|
+
nm = names[i]; gsub(/\\/, "\\\\", nm); gsub(/"/, "\\\"", nm); gsub(/\t/, "\\t", nm); gsub(/\n/, "\\n", nm); gsub(/\r/, "\\r", nm)
|
|
55
|
+
pr = parents[i]; gsub(/\\/, "\\\\", pr); gsub(/"/, "\\\"", pr); gsub(/\t/, "\\t", pr); gsub(/\n/, "\\n", pr); gsub(/\r/, "\\r", pr)
|
|
56
|
+
tk = tasks[i]; gsub(/\\/, "\\\\", tk); gsub(/"/, "\\\"", tk); gsub(/\t/, "\\t", tk); gsub(/\n/, "\\n", tk); gsub(/\r/, "\\r", tk)
|
|
57
57
|
printf "{\"name\":\"%s\",\"parent\":\"%s\",\"caste\":\"%s\",", nm, pr, castes[i]
|
|
58
58
|
printf "\"task\":\"%s\",\"status\":\"%s\",", tk, statuses[i]
|
|
59
59
|
printf "\"spawned_at\":\"%s\",\"completed_at\":\"%s\",", timestamps[i], completed_at[i]
|
|
@@ -63,7 +63,7 @@ parse_spawn_tree() {
|
|
|
63
63
|
for (j = 1; j <= length(cidxs); j++) {
|
|
64
64
|
if (j > 1) printf ","
|
|
65
65
|
cn = names[cidxs[j]+0]
|
|
66
|
-
gsub(/\\/, "\\\\", cn); gsub(/"/, "\\\"", cn); gsub(/\t/, "\\t", cn)
|
|
66
|
+
gsub(/\\/, "\\\\", cn); gsub(/"/, "\\\"", cn); gsub(/\t/, "\\t", cn); gsub(/\n/, "\\n", cn); gsub(/\r/, "\\r", cn)
|
|
67
67
|
printf "\"%s\"", cn
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -149,9 +149,9 @@ get_active_spawns() {
|
|
|
149
149
|
if (!(spawn_names[i] in done_set)) {
|
|
150
150
|
if (!first) printf ","
|
|
151
151
|
first = 0
|
|
152
|
-
nm = spawn_names[i]; gsub(/\\/, "\\\\", nm); gsub(/"/, "\\\"", nm); gsub(/\t/, "\\t", nm)
|
|
153
|
-
pr = spawn_parents[i]; gsub(/\\/, "\\\\", pr); gsub(/"/, "\\\"", pr); gsub(/\t/, "\\t", pr)
|
|
154
|
-
tk = spawn_tasks[i]; gsub(/\\/, "\\\\", tk); gsub(/"/, "\\\"", tk); gsub(/\t/, "\\t", tk)
|
|
152
|
+
nm = spawn_names[i]; gsub(/\\/, "\\\\", nm); gsub(/"/, "\\\"", nm); gsub(/\t/, "\\t", nm); gsub(/\n/, "\\n", nm); gsub(/\r/, "\\r", nm)
|
|
153
|
+
pr = spawn_parents[i]; gsub(/\\/, "\\\\", pr); gsub(/"/, "\\\"", pr); gsub(/\t/, "\\t", pr); gsub(/\n/, "\\n", pr); gsub(/\r/, "\\r", pr)
|
|
154
|
+
tk = spawn_tasks[i]; gsub(/\\/, "\\\\", tk); gsub(/"/, "\\\"", tk); gsub(/\t/, "\\t", tk); gsub(/\n/, "\\n", tk); gsub(/\r/, "\\r", tk)
|
|
155
155
|
printf "{\"name\":\"%s\",\"caste\":\"%s\",\"parent\":\"%s\",\"task\":\"%s\",\"spawned_at\":\"%s\"}", nm, spawn_castes[i], pr, tk, spawn_ts[i]
|
|
156
156
|
}
|
|
157
157
|
}
|
package/.aether/utils/spawn.sh
CHANGED
|
@@ -25,7 +25,7 @@ _spawn_log() {
|
|
|
25
25
|
emoji=$(get_caste_emoji "$child_caste")
|
|
26
26
|
parent_emoji=$(get_caste_emoji "$parent_id")
|
|
27
27
|
# Log to activity log with spawn format, emojis, and model info
|
|
28
|
-
echo "[$ts] ⚡ SPAWN $parent_emoji $parent_id -> $emoji $child_name ($child_caste): $task_summary [model: $model]" >> "$COLONY_DATA_DIR/activity.log"
|
|
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
29
|
# Log to spawn tree file for visualization (NEW FORMAT: includes model field)
|
|
30
30
|
echo "$ts_full|$parent_id|$child_caste|$child_name|$task_summary|$model|$status" >> "$COLONY_DATA_DIR/spawn-tree.txt"
|
|
31
31
|
# Return emoji-formatted result for display (jq-safe: child_name may contain JSON-special chars)
|
|
@@ -46,7 +46,7 @@ _spawn_complete() {
|
|
|
46
46
|
status_icon="✅"
|
|
47
47
|
[[ "$status" == "failed" ]] && status_icon="❌"
|
|
48
48
|
[[ "$status" == "blocked" ]] && status_icon="🚫"
|
|
49
|
-
echo "[$ts] $status_icon $emoji $ant_name: $status${summary:+ - $summary}" >> "$COLONY_DATA_DIR/activity.log"
|
|
49
|
+
[[ "${AETHER_TESTING:-}" != "1" ]] && echo "[$ts] $status_icon $emoji $ant_name: $status${summary:+ - $summary}" >> "$COLONY_DATA_DIR/activity.log"
|
|
50
50
|
# Update spawn tree
|
|
51
51
|
echo "$ts_full|$ant_name|$status|$summary" >> "$COLONY_DATA_DIR/spawn-tree.txt"
|
|
52
52
|
# Log failed spawns to events array as pipe-delimited strings (matching template format)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# State API facade -- centralized COLONY_STATE.json access
|
|
3
|
-
# Provides: _state_read, _state_write, _state_read_field, _state_mutate, _state_migrate
|
|
3
|
+
# Provides: _state_read, _state_write, _state_read_field, _state_mutate, _state_migrate,
|
|
4
|
+
# _colony_vital_signs
|
|
4
5
|
#
|
|
5
6
|
# These functions are sourced by aether-utils.sh at startup.
|
|
6
7
|
# All shared infrastructure (json_ok, json_err, atomic_write, acquire_lock,
|
|
@@ -197,3 +198,192 @@ _state_migrate() {
|
|
|
197
198
|
[[ "$sm_lock_held" == "true" ]] && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
198
199
|
fi
|
|
199
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
|
+
}
|
|
@@ -69,6 +69,8 @@ Read `.aether/data/COLONY_STATE.json`.
|
|
|
69
69
|
|
|
70
70
|
**If the file exists:** continue.
|
|
71
71
|
|
|
72
|
+
**If `milestone` == `"Crowned Anthill"`:** output "This colony has been sealed. Start a new colony with `/ant:init \"new goal\"`.", stop.
|
|
73
|
+
|
|
72
74
|
**If `plan.phases` is not empty:** output "Colony already has phases. Use /ant:continue.", stop.
|
|
73
75
|
|
|
74
76
|
### Step 2: Quick Surface Scan (for session context)
|