aether-colony 5.1.0 → 5.3.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.
Files changed (185) hide show
  1. package/.aether/aether-utils.sh +157 -42
  2. package/.aether/agents/aether-ambassador.md +140 -0
  3. package/.aether/agents/aether-archaeologist.md +108 -0
  4. package/.aether/agents/aether-architect.md +133 -0
  5. package/.aether/agents/aether-auditor.md +144 -0
  6. package/.aether/agents/aether-builder.md +184 -0
  7. package/.aether/agents/aether-chaos.md +115 -0
  8. package/.aether/agents/aether-chronicler.md +122 -0
  9. package/.aether/agents/aether-gatekeeper.md +116 -0
  10. package/.aether/agents/aether-includer.md +117 -0
  11. package/.aether/agents/aether-keeper.md +177 -0
  12. package/.aether/agents/aether-measurer.md +128 -0
  13. package/.aether/agents/aether-oracle.md +137 -0
  14. package/.aether/agents/aether-probe.md +133 -0
  15. package/.aether/agents/aether-queen.md +286 -0
  16. package/.aether/agents/aether-route-setter.md +130 -0
  17. package/.aether/agents/aether-sage.md +106 -0
  18. package/.aether/agents/aether-scout.md +101 -0
  19. package/.aether/agents/aether-surveyor-disciplines.md +391 -0
  20. package/.aether/agents/aether-surveyor-nest.md +329 -0
  21. package/.aether/agents/aether-surveyor-pathogens.md +264 -0
  22. package/.aether/agents/aether-surveyor-provisions.md +334 -0
  23. package/.aether/agents/aether-tracker.md +137 -0
  24. package/.aether/agents/aether-watcher.md +174 -0
  25. package/.aether/agents/aether-weaver.md +130 -0
  26. package/.aether/commands/claude/archaeology.md +334 -0
  27. package/.aether/commands/claude/build.md +65 -0
  28. package/.aether/commands/claude/chaos.md +336 -0
  29. package/.aether/commands/claude/colonize.md +259 -0
  30. package/.aether/commands/claude/continue.md +60 -0
  31. package/.aether/commands/claude/council.md +507 -0
  32. package/.aether/commands/claude/data-clean.md +81 -0
  33. package/.aether/commands/claude/dream.md +268 -0
  34. package/.aether/commands/claude/entomb.md +498 -0
  35. package/.aether/commands/claude/export-signals.md +57 -0
  36. package/.aether/commands/claude/feedback.md +96 -0
  37. package/.aether/commands/claude/flag.md +151 -0
  38. package/.aether/commands/claude/flags.md +169 -0
  39. package/.aether/commands/claude/focus.md +76 -0
  40. package/.aether/commands/claude/help.md +154 -0
  41. package/.aether/commands/claude/history.md +140 -0
  42. package/.aether/commands/claude/import-signals.md +71 -0
  43. package/.aether/commands/claude/init.md +505 -0
  44. package/.aether/commands/claude/insert-phase.md +105 -0
  45. package/.aether/commands/claude/interpret.md +278 -0
  46. package/.aether/commands/claude/lay-eggs.md +210 -0
  47. package/.aether/commands/claude/maturity.md +113 -0
  48. package/.aether/commands/claude/memory-details.md +77 -0
  49. package/.aether/commands/claude/migrate-state.md +171 -0
  50. package/.aether/commands/claude/oracle.md +642 -0
  51. package/.aether/commands/claude/organize.md +232 -0
  52. package/.aether/commands/claude/patrol.md +620 -0
  53. package/.aether/commands/claude/pause-colony.md +233 -0
  54. package/.aether/commands/claude/phase.md +115 -0
  55. package/.aether/commands/claude/pheromones.md +156 -0
  56. package/.aether/commands/claude/plan.md +693 -0
  57. package/.aether/commands/claude/preferences.md +65 -0
  58. package/.aether/commands/claude/quick.md +100 -0
  59. package/.aether/commands/claude/redirect.md +76 -0
  60. package/.aether/commands/claude/resume-colony.md +197 -0
  61. package/.aether/commands/claude/resume.md +388 -0
  62. package/.aether/commands/claude/run.md +231 -0
  63. package/.aether/commands/claude/seal.md +774 -0
  64. package/.aether/commands/claude/skill-create.md +286 -0
  65. package/.aether/commands/claude/status.md +410 -0
  66. package/.aether/commands/claude/swarm.md +349 -0
  67. package/.aether/commands/claude/tunnels.md +426 -0
  68. package/.aether/commands/claude/update.md +132 -0
  69. package/.aether/commands/claude/verify-castes.md +143 -0
  70. package/.aether/commands/claude/watch.md +239 -0
  71. package/.aether/commands/colonize.yaml +4 -0
  72. package/.aether/commands/council.yaml +205 -0
  73. package/.aether/commands/init.yaml +46 -13
  74. package/.aether/commands/insert-phase.yaml +4 -0
  75. package/.aether/commands/opencode/archaeology.md +331 -0
  76. package/.aether/commands/opencode/build.md +1168 -0
  77. package/.aether/commands/opencode/chaos.md +329 -0
  78. package/.aether/commands/opencode/colonize.md +195 -0
  79. package/.aether/commands/opencode/continue.md +1436 -0
  80. package/.aether/commands/opencode/council.md +437 -0
  81. package/.aether/commands/opencode/data-clean.md +77 -0
  82. package/.aether/commands/opencode/dream.md +260 -0
  83. package/.aether/commands/opencode/entomb.md +377 -0
  84. package/.aether/commands/opencode/export-signals.md +54 -0
  85. package/.aether/commands/opencode/feedback.md +99 -0
  86. package/.aether/commands/opencode/flag.md +149 -0
  87. package/.aether/commands/opencode/flags.md +167 -0
  88. package/.aether/commands/opencode/focus.md +73 -0
  89. package/.aether/commands/opencode/help.md +157 -0
  90. package/.aether/commands/opencode/history.md +136 -0
  91. package/.aether/commands/opencode/import-signals.md +68 -0
  92. package/.aether/commands/opencode/init.md +518 -0
  93. package/.aether/commands/opencode/insert-phase.md +111 -0
  94. package/.aether/commands/opencode/interpret.md +272 -0
  95. package/.aether/commands/opencode/lay-eggs.md +213 -0
  96. package/.aether/commands/opencode/maturity.md +108 -0
  97. package/.aether/commands/opencode/memory-details.md +83 -0
  98. package/.aether/commands/opencode/migrate-state.md +165 -0
  99. package/.aether/commands/opencode/oracle.md +593 -0
  100. package/.aether/commands/opencode/organize.md +226 -0
  101. package/.aether/commands/opencode/patrol.md +626 -0
  102. package/.aether/commands/opencode/pause-colony.md +203 -0
  103. package/.aether/commands/opencode/phase.md +113 -0
  104. package/.aether/commands/opencode/pheromones.md +162 -0
  105. package/.aether/commands/opencode/plan.md +684 -0
  106. package/.aether/commands/opencode/preferences.md +71 -0
  107. package/.aether/commands/opencode/quick.md +91 -0
  108. package/.aether/commands/opencode/redirect.md +84 -0
  109. package/.aether/commands/opencode/resume-colony.md +190 -0
  110. package/.aether/commands/opencode/resume.md +394 -0
  111. package/.aether/commands/opencode/run.md +237 -0
  112. package/.aether/commands/opencode/seal.md +452 -0
  113. package/.aether/commands/opencode/skill-create.md +63 -0
  114. package/.aether/commands/opencode/status.md +307 -0
  115. package/.aether/commands/opencode/swarm.md +15 -0
  116. package/.aether/commands/opencode/tunnels.md +400 -0
  117. package/.aether/commands/opencode/update.md +127 -0
  118. package/.aether/commands/opencode/verify-castes.md +139 -0
  119. package/.aether/commands/opencode/watch.md +227 -0
  120. package/.aether/commands/plan.yaml +53 -2
  121. package/.aether/commands/quick.yaml +104 -0
  122. package/.aether/commands/resume-colony.yaml +6 -4
  123. package/.aether/commands/resume.yaml +9 -0
  124. package/.aether/commands/run.yaml +37 -1
  125. package/.aether/commands/seal.yaml +9 -0
  126. package/.aether/commands/status.yaml +45 -1
  127. package/.aether/docs/command-playbooks/build-full.md +3 -2
  128. package/.aether/docs/command-playbooks/build-prep.md +12 -4
  129. package/.aether/docs/command-playbooks/build-verify.md +51 -0
  130. package/.aether/docs/command-playbooks/continue-advance.md +115 -6
  131. package/.aether/docs/command-playbooks/continue-full.md +1 -0
  132. package/.aether/docs/command-playbooks/continue-verify.md +33 -0
  133. package/.aether/utils/clash-detect.sh +239 -0
  134. package/.aether/utils/council.sh +425 -0
  135. package/.aether/utils/error-handler.sh +3 -3
  136. package/.aether/utils/flag.sh +23 -12
  137. package/.aether/utils/hive.sh +2 -2
  138. package/.aether/utils/hooks/clash-pre-tool-use.js +99 -0
  139. package/.aether/utils/immune.sh +508 -0
  140. package/.aether/utils/learning.sh +2 -2
  141. package/.aether/utils/merge-driver-lockfile.sh +35 -0
  142. package/.aether/utils/midden.sh +712 -0
  143. package/.aether/utils/pheromone.sh +1376 -108
  144. package/.aether/utils/queen.sh +31 -21
  145. package/.aether/utils/session.sh +264 -0
  146. package/.aether/utils/spawn-tree.sh +7 -7
  147. package/.aether/utils/spawn.sh +2 -2
  148. package/.aether/utils/state-api.sh +216 -5
  149. package/.aether/utils/swarm.sh +1 -1
  150. package/.aether/utils/worktree.sh +189 -0
  151. package/.claude/commands/ant/colonize.md +2 -0
  152. package/.claude/commands/ant/council.md +205 -0
  153. package/.claude/commands/ant/init.md +53 -14
  154. package/.claude/commands/ant/insert-phase.md +4 -0
  155. package/.claude/commands/ant/plan.md +27 -1
  156. package/.claude/commands/ant/quick.md +100 -0
  157. package/.claude/commands/ant/resume-colony.md +3 -2
  158. package/.claude/commands/ant/resume.md +9 -0
  159. package/.claude/commands/ant/run.md +37 -1
  160. package/.claude/commands/ant/seal.md +9 -0
  161. package/.claude/commands/ant/status.md +45 -1
  162. package/.opencode/commands/ant/colonize.md +2 -0
  163. package/.opencode/commands/ant/council.md +143 -0
  164. package/.opencode/commands/ant/init.md +53 -13
  165. package/.opencode/commands/ant/insert-phase.md +4 -0
  166. package/.opencode/commands/ant/plan.md +26 -1
  167. package/.opencode/commands/ant/quick.md +91 -0
  168. package/.opencode/commands/ant/resume-colony.md +3 -2
  169. package/.opencode/commands/ant/resume.md +9 -0
  170. package/.opencode/commands/ant/run.md +37 -1
  171. package/.opencode/commands/ant/status.md +2 -0
  172. package/CHANGELOG.md +116 -0
  173. package/README.md +34 -8
  174. package/bin/cli.js +103 -61
  175. package/bin/lib/banner.js +14 -0
  176. package/bin/lib/init.js +8 -7
  177. package/bin/lib/interactive-setup.js +251 -0
  178. package/bin/npx-entry.js +21 -0
  179. package/bin/npx-install.js +9 -167
  180. package/bin/validate-package.sh +23 -0
  181. package/package.json +11 -3
  182. package/.aether/docs/plans/pheromone-display-plan.md +0 -257
  183. package/.aether/schemas/example-prompt-builder.xml +0 -234
  184. package/.aether/scripts/incident-test-add.sh +0 -47
  185. package/.aether/scripts/weekly-audit.sh +0 -79
@@ -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
- awk -v line="$ev_separator" -v entry="$ev_entry" 'NR==line{print; print entry; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
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
- awk -v entry="$ev_log_entry" '
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\": [" entry "]"
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
- awk -v entry="$ev_log_entry" '
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\": [" entry "]")
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 entry
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 entry
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
- new_comment="<!-- METADATA"
633
- new_comment="$new_comment
634
- $updated_meta
635
- -->"
636
- awk -v new="$new_comment" '/<!-- METADATA/,/-->/{ if (/<!-- METADATA/) print new; next }1' "$tmp_file" > "${tmp_file}.metaupd" && mv "${tmp_file}.metaupd" "$tmp_file"
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
- awk -v line="$ev_separator" -v entry="$ev_entry" 'NR==line{print; print entry; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
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
- awk -v line="$ev_separator" -v entry="$ev_entry" 'NR==line{print; print entry; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
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
- awk -v line="$ev_separator" -v entry="$ev_entry" 'NR==line{print; print entry; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
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
@@ -1380,8 +1391,7 @@ _colony_depth() {
1380
1391
  local new_depth="${2:-}"
1381
1392
  case "$new_depth" in
1382
1393
  light|standard|deep|full)
1383
- local tmp="${DATA_DIR}/COLONY_STATE.json.tmp.$$"
1384
- jq --arg d "$new_depth" '.colony_depth = $d' "$DATA_DIR/COLONY_STATE.json" > "$tmp" && mv "$tmp" "$DATA_DIR/COLONY_STATE.json"
1394
+ NEW_DEPTH="$new_depth" _state_mutate '.colony_depth = env.NEW_DEPTH'
1385
1395
  json_ok "$(jq -n --arg depth "$new_depth" '{depth: $depth, updated: true}')"
1386
1396
  ;;
1387
1397
  *)
@@ -1465,8 +1475,7 @@ _queen_write_charter() {
1465
1475
  local current_name
1466
1476
  current_name=$(jq -r '.colony_name // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null) || true
1467
1477
  if [[ -z "$current_name" && -n "$cw_colony_name" ]]; then
1468
- local tmp_state="${DATA_DIR}/COLONY_STATE.json.tmp.$$"
1469
- jq --arg cn "$cw_colony_name" '.colony_name = $cn' "$DATA_DIR/COLONY_STATE.json" > "$tmp_state" && mv "$tmp_state" "$DATA_DIR/COLONY_STATE.json"
1478
+ CW_COLONY_NAME="$cw_colony_name" _state_mutate '.colony_name = env.CW_COLONY_NAME'
1470
1479
  fi
1471
1480
  fi
1472
1481
 
@@ -1601,7 +1610,8 @@ _queen_write_charter() {
1601
1610
  local ev_separator
1602
1611
  ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
1603
1612
  if [[ -n "$ev_separator" ]]; then
1604
- awk -v line="$ev_separator" -v entry="$ev_entry" 'NR==line{print; print entry; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
1613
+ # Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
1614
+ 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
1615
  fi
1606
1616
 
1607
1617
  # Update METADATA stats -- count non-charter list items in each section, add charter entries
@@ -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
  }
@@ -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)