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.
- package/.aether/aether-utils.sh +157 -42
- package/.aether/agents/aether-ambassador.md +140 -0
- package/.aether/agents/aether-archaeologist.md +108 -0
- package/.aether/agents/aether-architect.md +133 -0
- package/.aether/agents/aether-auditor.md +144 -0
- package/.aether/agents/aether-builder.md +184 -0
- package/.aether/agents/aether-chaos.md +115 -0
- package/.aether/agents/aether-chronicler.md +122 -0
- package/.aether/agents/aether-gatekeeper.md +116 -0
- package/.aether/agents/aether-includer.md +117 -0
- package/.aether/agents/aether-keeper.md +177 -0
- package/.aether/agents/aether-measurer.md +128 -0
- package/.aether/agents/aether-oracle.md +137 -0
- package/.aether/agents/aether-probe.md +133 -0
- package/.aether/agents/aether-queen.md +286 -0
- package/.aether/agents/aether-route-setter.md +130 -0
- package/.aether/agents/aether-sage.md +106 -0
- package/.aether/agents/aether-scout.md +101 -0
- package/.aether/agents/aether-surveyor-disciplines.md +391 -0
- package/.aether/agents/aether-surveyor-nest.md +329 -0
- package/.aether/agents/aether-surveyor-pathogens.md +264 -0
- package/.aether/agents/aether-surveyor-provisions.md +334 -0
- package/.aether/agents/aether-tracker.md +137 -0
- package/.aether/agents/aether-watcher.md +174 -0
- package/.aether/agents/aether-weaver.md +130 -0
- package/.aether/commands/claude/archaeology.md +334 -0
- package/.aether/commands/claude/build.md +65 -0
- package/.aether/commands/claude/chaos.md +336 -0
- package/.aether/commands/claude/colonize.md +259 -0
- package/.aether/commands/claude/continue.md +60 -0
- package/.aether/commands/claude/council.md +507 -0
- package/.aether/commands/claude/data-clean.md +81 -0
- package/.aether/commands/claude/dream.md +268 -0
- package/.aether/commands/claude/entomb.md +498 -0
- package/.aether/commands/claude/export-signals.md +57 -0
- package/.aether/commands/claude/feedback.md +96 -0
- package/.aether/commands/claude/flag.md +151 -0
- package/.aether/commands/claude/flags.md +169 -0
- package/.aether/commands/claude/focus.md +76 -0
- package/.aether/commands/claude/help.md +154 -0
- package/.aether/commands/claude/history.md +140 -0
- package/.aether/commands/claude/import-signals.md +71 -0
- package/.aether/commands/claude/init.md +505 -0
- package/.aether/commands/claude/insert-phase.md +105 -0
- package/.aether/commands/claude/interpret.md +278 -0
- package/.aether/commands/claude/lay-eggs.md +210 -0
- package/.aether/commands/claude/maturity.md +113 -0
- package/.aether/commands/claude/memory-details.md +77 -0
- package/.aether/commands/claude/migrate-state.md +171 -0
- package/.aether/commands/claude/oracle.md +642 -0
- package/.aether/commands/claude/organize.md +232 -0
- package/.aether/commands/claude/patrol.md +620 -0
- package/.aether/commands/claude/pause-colony.md +233 -0
- package/.aether/commands/claude/phase.md +115 -0
- package/.aether/commands/claude/pheromones.md +156 -0
- package/.aether/commands/claude/plan.md +693 -0
- package/.aether/commands/claude/preferences.md +65 -0
- package/.aether/commands/claude/quick.md +100 -0
- package/.aether/commands/claude/redirect.md +76 -0
- package/.aether/commands/claude/resume-colony.md +197 -0
- package/.aether/commands/claude/resume.md +388 -0
- package/.aether/commands/claude/run.md +231 -0
- package/.aether/commands/claude/seal.md +774 -0
- package/.aether/commands/claude/skill-create.md +286 -0
- package/.aether/commands/claude/status.md +410 -0
- package/.aether/commands/claude/swarm.md +349 -0
- package/.aether/commands/claude/tunnels.md +426 -0
- package/.aether/commands/claude/update.md +132 -0
- package/.aether/commands/claude/verify-castes.md +143 -0
- package/.aether/commands/claude/watch.md +239 -0
- 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/opencode/archaeology.md +331 -0
- package/.aether/commands/opencode/build.md +1168 -0
- package/.aether/commands/opencode/chaos.md +329 -0
- package/.aether/commands/opencode/colonize.md +195 -0
- package/.aether/commands/opencode/continue.md +1436 -0
- package/.aether/commands/opencode/council.md +437 -0
- package/.aether/commands/opencode/data-clean.md +77 -0
- package/.aether/commands/opencode/dream.md +260 -0
- package/.aether/commands/opencode/entomb.md +377 -0
- package/.aether/commands/opencode/export-signals.md +54 -0
- package/.aether/commands/opencode/feedback.md +99 -0
- package/.aether/commands/opencode/flag.md +149 -0
- package/.aether/commands/opencode/flags.md +167 -0
- package/.aether/commands/opencode/focus.md +73 -0
- package/.aether/commands/opencode/help.md +157 -0
- package/.aether/commands/opencode/history.md +136 -0
- package/.aether/commands/opencode/import-signals.md +68 -0
- package/.aether/commands/opencode/init.md +518 -0
- package/.aether/commands/opencode/insert-phase.md +111 -0
- package/.aether/commands/opencode/interpret.md +272 -0
- package/.aether/commands/opencode/lay-eggs.md +213 -0
- package/.aether/commands/opencode/maturity.md +108 -0
- package/.aether/commands/opencode/memory-details.md +83 -0
- package/.aether/commands/opencode/migrate-state.md +165 -0
- package/.aether/commands/opencode/oracle.md +593 -0
- package/.aether/commands/opencode/organize.md +226 -0
- package/.aether/commands/opencode/patrol.md +626 -0
- package/.aether/commands/opencode/pause-colony.md +203 -0
- package/.aether/commands/opencode/phase.md +113 -0
- package/.aether/commands/opencode/pheromones.md +162 -0
- package/.aether/commands/opencode/plan.md +684 -0
- package/.aether/commands/opencode/preferences.md +71 -0
- package/.aether/commands/opencode/quick.md +91 -0
- package/.aether/commands/opencode/redirect.md +84 -0
- package/.aether/commands/opencode/resume-colony.md +190 -0
- package/.aether/commands/opencode/resume.md +394 -0
- package/.aether/commands/opencode/run.md +237 -0
- package/.aether/commands/opencode/seal.md +452 -0
- package/.aether/commands/opencode/skill-create.md +63 -0
- package/.aether/commands/opencode/status.md +307 -0
- package/.aether/commands/opencode/swarm.md +15 -0
- package/.aether/commands/opencode/tunnels.md +400 -0
- package/.aether/commands/opencode/update.md +127 -0
- package/.aether/commands/opencode/verify-castes.md +139 -0
- package/.aether/commands/opencode/watch.md +227 -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 +3 -2
- package/.aether/docs/command-playbooks/build-prep.md +12 -4
- package/.aether/docs/command-playbooks/build-verify.md +51 -0
- package/.aether/docs/command-playbooks/continue-advance.md +115 -6
- package/.aether/docs/command-playbooks/continue-full.md +1 -0
- package/.aether/docs/command-playbooks/continue-verify.md +33 -0
- package/.aether/utils/clash-detect.sh +239 -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/hooks/clash-pre-tool-use.js +99 -0
- package/.aether/utils/immune.sh +508 -0
- package/.aether/utils/learning.sh +2 -2
- package/.aether/utils/merge-driver-lockfile.sh +35 -0
- package/.aether/utils/midden.sh +712 -0
- package/.aether/utils/pheromone.sh +1376 -108
- package/.aether/utils/queen.sh +31 -21
- 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 +216 -5
- package/.aether/utils/swarm.sh +1 -1
- package/.aether/utils/worktree.sh +189 -0
- package/.claude/commands/ant/colonize.md +2 -0
- package/.claude/commands/ant/council.md +205 -0
- package/.claude/commands/ant/init.md +53 -14
- 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 +53 -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 +116 -0
- package/README.md +34 -8
- package/bin/cli.js +103 -61
- package/bin/lib/banner.js +14 -0
- package/bin/lib/init.js +8 -7
- package/bin/lib/interactive-setup.js +251 -0
- package/bin/npx-entry.js +21 -0
- package/bin/npx-install.js +9 -167
- package/bin/validate-package.sh +23 -0
- package/package.json +11 -3
- package/.aether/docs/plans/pheromone-display-plan.md +0 -257
- package/.aether/schemas/example-prompt-builder.xml +0 -234
- package/.aether/scripts/incident-test-add.sh +0 -47
- package/.aether/scripts/weekly-audit.sh +0 -79
|
@@ -730,6 +730,161 @@ pp_log_json=$(printf '%s' "$pp_log_line" | jq -Rs '.' 2>/dev/null || echo '"Prim
|
|
|
730
730
|
json_ok "$(jq -n --argjson signal_count "$pp_signal_count" --argjson instinct_count "$pp_instinct_count" --argjson prompt_section "$pp_section_json" --argjson log_line "$pp_log_json" '{signal_count: $signal_count, instinct_count: $instinct_count, prompt_section: $prompt_section, log_line: $log_line}')"
|
|
731
731
|
}
|
|
732
732
|
|
|
733
|
+
# ============================================================================
|
|
734
|
+
# _budget_enforce
|
|
735
|
+
# Shared budget enforcement for colony-prime and pr-context.
|
|
736
|
+
# Trims sections in priority order when assembled prompt exceeds character budget.
|
|
737
|
+
# Usage: _budget_enforce "<prefix>"
|
|
738
|
+
# prefix: variable prefix ("cp_" for colony-prime, "pc_" for pr-context)
|
|
739
|
+
# Reads/writes via indirect access:
|
|
740
|
+
# {prefix}max_chars, {prefix}budget_len, {prefix}final_prompt,
|
|
741
|
+
# {prefix}sec_rolling, {prefix}sec_learnings, {prefix}sec_decisions,
|
|
742
|
+
# {prefix}sec_hive, {prefix}sec_capsule, {prefix}sec_user_prefs,
|
|
743
|
+
# {prefix}sec_queen_global, {prefix}sec_queen_local, {prefix}sec_signals,
|
|
744
|
+
# {prefix}sec_blockers, {prefix}budget_trimmed_list
|
|
745
|
+
# Trim order: rolling > learnings > decisions > hive > capsule > user_prefs >
|
|
746
|
+
# queen_global > queen_local > signals (preserves REDIRECTs). NEVER trims blockers.
|
|
747
|
+
# ============================================================================
|
|
748
|
+
_budget_enforce() {
|
|
749
|
+
local _be_prefix="${1:-cp_}"
|
|
750
|
+
|
|
751
|
+
# Assemble final_prompt from sections
|
|
752
|
+
eval "local _be_sec_queen_global=\"\${${_be_prefix}sec_queen_global}\""
|
|
753
|
+
eval "local _be_sec_queen_local=\"\${${_be_prefix}sec_queen_local}\""
|
|
754
|
+
eval "local _be_sec_user_prefs=\"\${${_be_prefix}sec_user_prefs}\""
|
|
755
|
+
eval "local _be_sec_hive=\"\${${_be_prefix}sec_hive}\""
|
|
756
|
+
eval "local _be_sec_capsule=\"\${${_be_prefix}sec_capsule}\""
|
|
757
|
+
eval "local _be_sec_learnings=\"\${${_be_prefix}sec_learnings}\""
|
|
758
|
+
eval "local _be_sec_decisions=\"\${${_be_prefix}sec_decisions}\""
|
|
759
|
+
eval "local _be_sec_blockers=\"\${${_be_prefix}sec_blockers}\""
|
|
760
|
+
eval "local _be_sec_rolling=\"\${${_be_prefix}sec_rolling}\""
|
|
761
|
+
eval "local _be_sec_signals=\"\${${_be_prefix}sec_signals}\""
|
|
762
|
+
|
|
763
|
+
eval "local _be_max_chars=\${${_be_prefix}max_chars}"
|
|
764
|
+
|
|
765
|
+
# Assemble all sections in order
|
|
766
|
+
local _be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
767
|
+
|
|
768
|
+
local _be_budget_len=${#_be_final_prompt}
|
|
769
|
+
local _be_trimmed_list=""
|
|
770
|
+
|
|
771
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" ]]; then
|
|
772
|
+
# Over budget -- trim sections in priority order (first = trimmed first)
|
|
773
|
+
|
|
774
|
+
# 1. Trim rolling-summary
|
|
775
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_rolling" ]]; then
|
|
776
|
+
_be_sec_rolling=""
|
|
777
|
+
_be_trimmed_list="rolling-summary"
|
|
778
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
779
|
+
_be_budget_len=${#_be_final_prompt}
|
|
780
|
+
fi
|
|
781
|
+
|
|
782
|
+
# 2. Trim phase-learnings
|
|
783
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_learnings" ]]; then
|
|
784
|
+
_be_sec_learnings=""
|
|
785
|
+
_be_trimmed_list="${_be_trimmed_list:+$_be_trimmed_list,}phase-learnings"
|
|
786
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
787
|
+
_be_budget_len=${#_be_final_prompt}
|
|
788
|
+
fi
|
|
789
|
+
|
|
790
|
+
# 3. Trim key-decisions
|
|
791
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_decisions" ]]; then
|
|
792
|
+
_be_sec_decisions=""
|
|
793
|
+
_be_trimmed_list="${_be_trimmed_list:+$_be_trimmed_list,}key-decisions"
|
|
794
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
795
|
+
_be_budget_len=${#_be_final_prompt}
|
|
796
|
+
fi
|
|
797
|
+
|
|
798
|
+
# 4. Trim hive-wisdom
|
|
799
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_hive" ]]; then
|
|
800
|
+
_be_sec_hive=""
|
|
801
|
+
_be_trimmed_list="${_be_trimmed_list:+$_be_trimmed_list,}hive-wisdom"
|
|
802
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
803
|
+
_be_budget_len=${#_be_final_prompt}
|
|
804
|
+
fi
|
|
805
|
+
|
|
806
|
+
# 5. Trim context-capsule
|
|
807
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_capsule" ]]; then
|
|
808
|
+
_be_sec_capsule=""
|
|
809
|
+
_be_trimmed_list="${_be_trimmed_list:+$_be_trimmed_list,}context-capsule"
|
|
810
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
811
|
+
_be_budget_len=${#_be_final_prompt}
|
|
812
|
+
fi
|
|
813
|
+
|
|
814
|
+
# 6. Trim user-prefs
|
|
815
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_user_prefs" ]]; then
|
|
816
|
+
_be_sec_user_prefs=""
|
|
817
|
+
_be_trimmed_list="${_be_trimmed_list:+$_be_trimmed_list,}user-prefs"
|
|
818
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
819
|
+
_be_budget_len=${#_be_final_prompt}
|
|
820
|
+
fi
|
|
821
|
+
|
|
822
|
+
# 7. Trim queen-wisdom-global (trim global before local -- local is more relevant)
|
|
823
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_queen_global" ]]; then
|
|
824
|
+
_be_sec_queen_global=""
|
|
825
|
+
_be_trimmed_list="${_be_trimmed_list:+$_be_trimmed_list,}queen-wisdom-global"
|
|
826
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
827
|
+
_be_budget_len=${#_be_final_prompt}
|
|
828
|
+
fi
|
|
829
|
+
|
|
830
|
+
# 8. Trim queen-wisdom-local
|
|
831
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_queen_local" ]]; then
|
|
832
|
+
_be_sec_queen_local=""
|
|
833
|
+
_be_trimmed_list="${_be_trimmed_list:+$_be_trimmed_list,}queen-wisdom-local"
|
|
834
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
835
|
+
_be_budget_len=${#_be_final_prompt}
|
|
836
|
+
fi
|
|
837
|
+
|
|
838
|
+
# 9. Trim pheromone-signals (preserve REDIRECTs)
|
|
839
|
+
if [[ "$_be_budget_len" -gt "$_be_max_chars" && -n "$_be_sec_signals" ]]; then
|
|
840
|
+
# Extract REDIRECT lines and preserve them
|
|
841
|
+
local _be_redirect_preserved=""
|
|
842
|
+
if [[ "$_be_sec_signals" == *"REDIRECT (HARD CONSTRAINTS"* ]]; then
|
|
843
|
+
local _be_redirect_lines=""
|
|
844
|
+
local _be_in_redirect=false
|
|
845
|
+
local _be_rl
|
|
846
|
+
while IFS= read -r _be_rl; do
|
|
847
|
+
if [[ "$_be_rl" == *"REDIRECT (HARD CONSTRAINTS"* ]]; then
|
|
848
|
+
_be_in_redirect=true
|
|
849
|
+
_be_redirect_lines+="$_be_rl"$'\n'
|
|
850
|
+
elif [[ "$_be_in_redirect" == "true" ]]; then
|
|
851
|
+
if [[ "$_be_rl" == "FOCUS "* ]] || [[ "$_be_rl" == "FEEDBACK "* ]] || \
|
|
852
|
+
[[ "$_be_rl" == "POSITION "* ]] || [[ "$_be_rl" == "--- "* ]]; then
|
|
853
|
+
_be_in_redirect=false
|
|
854
|
+
else
|
|
855
|
+
_be_redirect_lines+="$_be_rl"$'\n'
|
|
856
|
+
fi
|
|
857
|
+
fi
|
|
858
|
+
done <<< "$_be_sec_signals"
|
|
859
|
+
if [[ -n "$_be_redirect_lines" ]]; then
|
|
860
|
+
_be_redirect_preserved=$'\n'"--- ACTIVE SIGNALS (Colony Guidance) ---"$'\n'
|
|
861
|
+
_be_redirect_preserved+=$'\n'"$_be_redirect_lines"
|
|
862
|
+
_be_redirect_preserved+=$'\n'"--- END COLONY CONTEXT ---"
|
|
863
|
+
fi
|
|
864
|
+
fi
|
|
865
|
+
_be_sec_signals="$_be_redirect_preserved"
|
|
866
|
+
_be_trimmed_list="${_be_trimmed_list:+$_be_trimmed_list,}pheromone-signals"
|
|
867
|
+
_be_final_prompt="$_be_sec_queen_global$_be_sec_queen_local$_be_sec_user_prefs$_be_sec_hive$_be_sec_capsule$_be_sec_learnings$_be_sec_decisions$_be_sec_blockers$_be_sec_rolling$_be_sec_signals"
|
|
868
|
+
_be_budget_len=${#_be_final_prompt}
|
|
869
|
+
fi
|
|
870
|
+
fi
|
|
871
|
+
|
|
872
|
+
# Write back to caller's variables
|
|
873
|
+
eval "${_be_prefix}sec_queen_global=\"\$_be_sec_queen_global\""
|
|
874
|
+
eval "${_be_prefix}sec_queen_local=\"\$_be_sec_queen_local\""
|
|
875
|
+
eval "${_be_prefix}sec_user_prefs=\"\$_be_sec_user_prefs\""
|
|
876
|
+
eval "${_be_prefix}sec_hive=\"\$_be_sec_hive\""
|
|
877
|
+
eval "${_be_prefix}sec_capsule=\"\$_be_sec_capsule\""
|
|
878
|
+
eval "${_be_prefix}sec_learnings=\"\$_be_sec_learnings\""
|
|
879
|
+
eval "${_be_prefix}sec_decisions=\"\$_be_sec_decisions\""
|
|
880
|
+
eval "${_be_prefix}sec_blockers=\"\$_be_sec_blockers\""
|
|
881
|
+
eval "${_be_prefix}sec_rolling=\"\$_be_sec_rolling\""
|
|
882
|
+
eval "${_be_prefix}sec_signals=\"\$_be_sec_signals\""
|
|
883
|
+
eval "${_be_prefix}final_prompt=\"\$_be_final_prompt\""
|
|
884
|
+
eval "${_be_prefix}budget_len=\"\$_be_budget_len\""
|
|
885
|
+
eval "${_be_prefix}budget_trimmed_list=\"\$_be_trimmed_list\""
|
|
886
|
+
}
|
|
887
|
+
|
|
733
888
|
# ============================================================================
|
|
734
889
|
# _colony_prime
|
|
735
890
|
# Unified colony priming: combines wisdom (QUEEN.md) + signals + instincts into single output
|
|
@@ -1380,115 +1535,11 @@ fi
|
|
|
1380
1535
|
# context-capsule > user-prefs > queen-wisdom-global > queen-wisdom-local > pheromone-signals (NEVER trim REDIRECTs)
|
|
1381
1536
|
# Blockers are always kept (REDIRECT-priority).
|
|
1382
1537
|
|
|
1383
|
-
|
|
1384
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1385
|
-
|
|
1386
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1387
|
-
|
|
1388
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" ]]; then
|
|
1389
|
-
# Over budget -- trim sections in priority order (first = trimmed first)
|
|
1390
|
-
cp_budget_trimmed_list=""
|
|
1391
|
-
|
|
1392
|
-
# 1. Trim rolling-summary
|
|
1393
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" && -n "$cp_sec_rolling" ]]; then
|
|
1394
|
-
cp_sec_rolling=""
|
|
1395
|
-
cp_budget_trimmed_list="rolling-summary"
|
|
1396
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1397
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1398
|
-
fi
|
|
1399
|
-
|
|
1400
|
-
# 2. Trim phase-learnings
|
|
1401
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" && -n "$cp_sec_learnings" ]]; then
|
|
1402
|
-
cp_sec_learnings=""
|
|
1403
|
-
cp_budget_trimmed_list="${cp_budget_trimmed_list:+$cp_budget_trimmed_list,}phase-learnings"
|
|
1404
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1405
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1406
|
-
fi
|
|
1407
|
-
|
|
1408
|
-
# 3. Trim key-decisions
|
|
1409
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" && -n "$cp_sec_decisions" ]]; then
|
|
1410
|
-
cp_sec_decisions=""
|
|
1411
|
-
cp_budget_trimmed_list="${cp_budget_trimmed_list:+$cp_budget_trimmed_list,}key-decisions"
|
|
1412
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1413
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1414
|
-
fi
|
|
1415
|
-
|
|
1416
|
-
# 4. Trim hive-wisdom
|
|
1417
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" && -n "$cp_sec_hive" ]]; then
|
|
1418
|
-
cp_sec_hive=""
|
|
1419
|
-
cp_budget_trimmed_list="${cp_budget_trimmed_list:+$cp_budget_trimmed_list,}hive-wisdom"
|
|
1420
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1421
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1422
|
-
fi
|
|
1423
|
-
|
|
1424
|
-
# 5. Trim context-capsule
|
|
1425
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" && -n "$cp_sec_capsule" ]]; then
|
|
1426
|
-
cp_sec_capsule=""
|
|
1427
|
-
cp_budget_trimmed_list="${cp_budget_trimmed_list:+$cp_budget_trimmed_list,}context-capsule"
|
|
1428
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1429
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1430
|
-
fi
|
|
1431
|
-
|
|
1432
|
-
# 6. Trim user-prefs
|
|
1433
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" && -n "$cp_sec_user_prefs" ]]; then
|
|
1434
|
-
cp_sec_user_prefs=""
|
|
1435
|
-
cp_budget_trimmed_list="${cp_budget_trimmed_list:+$cp_budget_trimmed_list,}user-prefs"
|
|
1436
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1437
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1438
|
-
fi
|
|
1439
|
-
|
|
1440
|
-
# 7. Trim queen-wisdom-global (trim global before local -- local is more relevant)
|
|
1441
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" && -n "$cp_sec_queen_global" ]]; then
|
|
1442
|
-
cp_sec_queen_global=""
|
|
1443
|
-
cp_budget_trimmed_list="${cp_budget_trimmed_list:+$cp_budget_trimmed_list,}queen-wisdom-global"
|
|
1444
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1445
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1446
|
-
fi
|
|
1447
|
-
|
|
1448
|
-
# 8. Trim queen-wisdom-local
|
|
1449
|
-
if [[ "$cp_budget_len" -gt "$cp_max_chars" && -n "$cp_sec_queen_local" ]]; then
|
|
1450
|
-
cp_sec_queen_local=""
|
|
1451
|
-
cp_budget_trimmed_list="${cp_budget_trimmed_list:+$cp_budget_trimmed_list,}queen-wisdom-local"
|
|
1452
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1453
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1454
|
-
fi
|
|
1538
|
+
_budget_enforce "cp_"
|
|
1455
1539
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
cp_redirect_preserved=""
|
|
1460
|
-
if [[ "$cp_sec_signals" == *"REDIRECT (HARD CONSTRAINTS"* ]]; then
|
|
1461
|
-
cp_redirect_lines=""
|
|
1462
|
-
cp_in_redirect=false
|
|
1463
|
-
while IFS= read -r cp_rl; do
|
|
1464
|
-
if [[ "$cp_rl" == *"REDIRECT (HARD CONSTRAINTS"* ]]; then
|
|
1465
|
-
cp_in_redirect=true
|
|
1466
|
-
cp_redirect_lines+="$cp_rl"$'\n'
|
|
1467
|
-
elif [[ "$cp_in_redirect" == "true" ]]; then
|
|
1468
|
-
if [[ "$cp_rl" == "FOCUS "* ]] || [[ "$cp_rl" == "FEEDBACK "* ]] || \
|
|
1469
|
-
[[ "$cp_rl" == "POSITION "* ]] || [[ "$cp_rl" == "--- "* ]]; then
|
|
1470
|
-
cp_in_redirect=false
|
|
1471
|
-
else
|
|
1472
|
-
cp_redirect_lines+="$cp_rl"$'\n'
|
|
1473
|
-
fi
|
|
1474
|
-
fi
|
|
1475
|
-
done <<< "$cp_sec_signals"
|
|
1476
|
-
if [[ -n "$cp_redirect_lines" ]]; then
|
|
1477
|
-
cp_redirect_preserved=$'\n'"--- ACTIVE SIGNALS (Colony Guidance) ---"$'\n'
|
|
1478
|
-
cp_redirect_preserved+=$'\n'"$cp_redirect_lines"
|
|
1479
|
-
cp_redirect_preserved+=$'\n'"--- END COLONY CONTEXT ---"
|
|
1480
|
-
fi
|
|
1481
|
-
fi
|
|
1482
|
-
cp_sec_signals="$cp_redirect_preserved"
|
|
1483
|
-
cp_budget_trimmed_list="${cp_budget_trimmed_list:+$cp_budget_trimmed_list,}pheromone-signals"
|
|
1484
|
-
cp_final_prompt="$cp_sec_queen_global$cp_sec_queen_local$cp_sec_user_prefs$cp_sec_hive$cp_sec_capsule$cp_sec_learnings$cp_sec_decisions$cp_sec_blockers$cp_sec_rolling$cp_sec_signals"
|
|
1485
|
-
cp_budget_len=${#cp_final_prompt}
|
|
1486
|
-
fi
|
|
1487
|
-
|
|
1488
|
-
# Append truncation note to log line
|
|
1489
|
-
if [[ -n "$cp_budget_trimmed_list" ]]; then
|
|
1490
|
-
cp_log_line="$cp_log_line, truncated: $cp_budget_trimmed_list (budget: ${cp_max_chars})"
|
|
1491
|
-
fi
|
|
1540
|
+
# Append truncation note to log line (post _budget_enforce)
|
|
1541
|
+
if [[ -n "${cp_budget_trimmed_list:-}" ]]; then
|
|
1542
|
+
cp_log_line="$cp_log_line, truncated: $cp_budget_trimmed_list (budget: ${cp_max_chars})"
|
|
1492
1543
|
fi
|
|
1493
1544
|
# === END Budget enforcement ===
|
|
1494
1545
|
|
|
@@ -1552,6 +1603,569 @@ fi
|
|
|
1552
1603
|
json_ok "$cp_result"
|
|
1553
1604
|
}
|
|
1554
1605
|
|
|
1606
|
+
# ============================================================================
|
|
1607
|
+
# _cache_read / _cache_write
|
|
1608
|
+
# Cache helpers for pr-context -- TTL-based with mtime validation
|
|
1609
|
+
# ============================================================================
|
|
1610
|
+
_cache_read() {
|
|
1611
|
+
local _cr_name="$1"
|
|
1612
|
+
local _cr_path="$2"
|
|
1613
|
+
local _cr_ttl="$3"
|
|
1614
|
+
local _cr_cache_file="${COLONY_DATA_DIR:-$DATA_DIR}/pr-context-cache.json"
|
|
1615
|
+
|
|
1616
|
+
if [[ ! -f "$_cr_cache_file" ]]; then
|
|
1617
|
+
echo "null"
|
|
1618
|
+
return 0
|
|
1619
|
+
fi
|
|
1620
|
+
|
|
1621
|
+
# Get source file mtime
|
|
1622
|
+
local _cr_mtime
|
|
1623
|
+
_cr_mtime=$(stat -f "%m" "$_cr_path" 2>/dev/null || stat -c "%Y" "$_cr_path" 2>/dev/null || echo "0")
|
|
1624
|
+
|
|
1625
|
+
# Get cached entry
|
|
1626
|
+
local _cr_entry
|
|
1627
|
+
_cr_entry=$(jq -r --arg name "$_cr_name" '.[$name] // null' "$_cr_cache_file" 2>/dev/null)
|
|
1628
|
+
|
|
1629
|
+
if [[ "$_cr_entry" == "null" || -z "$_cr_entry" ]]; then
|
|
1630
|
+
echo "null"
|
|
1631
|
+
return 0
|
|
1632
|
+
fi
|
|
1633
|
+
|
|
1634
|
+
# Check mtime match
|
|
1635
|
+
local _cr_cached_mtime
|
|
1636
|
+
_cr_cached_mtime=$(echo "$_cr_entry" | jq -r '.mtime // 0' 2>/dev/null)
|
|
1637
|
+
if [[ "$_cr_cached_mtime" != "$_cr_mtime" ]]; then
|
|
1638
|
+
echo "null"
|
|
1639
|
+
return 0
|
|
1640
|
+
fi
|
|
1641
|
+
|
|
1642
|
+
# Check TTL
|
|
1643
|
+
local _cr_cached_at
|
|
1644
|
+
_cr_cached_at=$(echo "$_cr_entry" | jq -r '.cached_at // 0' 2>/dev/null)
|
|
1645
|
+
local _cr_now
|
|
1646
|
+
_cr_now=$(date +%s)
|
|
1647
|
+
local _cr_age=$(( _cr_now - _cr_cached_at ))
|
|
1648
|
+
if [[ "$_cr_age" -gt "$_cr_ttl" ]]; then
|
|
1649
|
+
echo "null"
|
|
1650
|
+
return 0
|
|
1651
|
+
fi
|
|
1652
|
+
|
|
1653
|
+
# Cache hit -- return the data
|
|
1654
|
+
echo "$_cr_entry" | jq -r '.data'
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
_cache_write() {
|
|
1658
|
+
local _cw_name="$1"
|
|
1659
|
+
local _cw_path="$2"
|
|
1660
|
+
local _cw_data="$3"
|
|
1661
|
+
local _cw_cache_file="${COLONY_DATA_DIR:-$DATA_DIR}/pr-context-cache.json"
|
|
1662
|
+
|
|
1663
|
+
# Ensure directory exists
|
|
1664
|
+
mkdir -p "$(dirname "$_cw_cache_file")" 2>/dev/null || true
|
|
1665
|
+
|
|
1666
|
+
# Get source file mtime
|
|
1667
|
+
local _cw_mtime
|
|
1668
|
+
_cw_mtime=$(stat -f "%m" "$_cw_path" 2>/dev/null || stat -c "%Y" "$_cw_path" 2>/dev/null || echo "0")
|
|
1669
|
+
|
|
1670
|
+
local _cw_now
|
|
1671
|
+
_cw_now=$(date +%s)
|
|
1672
|
+
|
|
1673
|
+
# Build new entry
|
|
1674
|
+
local _cw_entry
|
|
1675
|
+
_cw_entry=$(jq -n \
|
|
1676
|
+
--arg path "$_cw_path" \
|
|
1677
|
+
--arg mtime "$_cw_mtime" \
|
|
1678
|
+
--argjson cached_at "$_cw_now" \
|
|
1679
|
+
--argjson data "$_cw_data" \
|
|
1680
|
+
'{path: $path, mtime: $mtime, cached_at: $cached_at, data: $data}')
|
|
1681
|
+
|
|
1682
|
+
# Merge into cache file
|
|
1683
|
+
if [[ ! -f "$_cw_cache_file" ]]; then
|
|
1684
|
+
echo "{}" > "$_cw_cache_file"
|
|
1685
|
+
fi
|
|
1686
|
+
|
|
1687
|
+
# Use atomic write pattern
|
|
1688
|
+
local _cw_tmp="${_cw_cache_file}.tmp.$$"
|
|
1689
|
+
jq --arg name "$_cw_name" --argjson entry "$_cw_entry" \
|
|
1690
|
+
'.[$name] = $entry' "$_cw_cache_file" > "$_cw_tmp" 2>/dev/null && \
|
|
1691
|
+
mv "$_cw_tmp" "$_cw_cache_file" 2>/dev/null || \
|
|
1692
|
+
rm -f "$_cw_tmp" 2>/dev/null
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
# ============================================================================
|
|
1696
|
+
# _pr_context
|
|
1697
|
+
# Generate CI-ready colony context as structured JSON.
|
|
1698
|
+
# Soft-fails on every missing source. Uses cache for stable sources.
|
|
1699
|
+
# ============================================================================
|
|
1700
|
+
_pr_context() {
|
|
1701
|
+
pc_compact=false
|
|
1702
|
+
pc_branch=""
|
|
1703
|
+
pc_ci_run_id=""
|
|
1704
|
+
|
|
1705
|
+
# Parse flags
|
|
1706
|
+
while [[ $# -gt 0 ]]; do
|
|
1707
|
+
case "$1" in
|
|
1708
|
+
--compact) pc_compact=true; shift ;;
|
|
1709
|
+
--branch) pc_branch="${2:-}"; shift 2 ;;
|
|
1710
|
+
--ci-run-id) pc_ci_run_id="${2:-}"; shift 2 ;;
|
|
1711
|
+
*) shift ;;
|
|
1712
|
+
esac
|
|
1713
|
+
done
|
|
1714
|
+
|
|
1715
|
+
# Defaults
|
|
1716
|
+
pc_max_chars=6000
|
|
1717
|
+
if [[ "$pc_compact" == "true" ]]; then
|
|
1718
|
+
pc_max_chars=3000
|
|
1719
|
+
fi
|
|
1720
|
+
|
|
1721
|
+
if [[ -z "$pc_branch" ]]; then
|
|
1722
|
+
pc_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
1723
|
+
fi
|
|
1724
|
+
|
|
1725
|
+
pc_generated_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1726
|
+
pc_warnings=()
|
|
1727
|
+
pc_fallbacks=()
|
|
1728
|
+
pc_cache_status='{}'
|
|
1729
|
+
|
|
1730
|
+
# === Section: queen (global) ===
|
|
1731
|
+
pc_queen_global_file="$HOME/.aether/QUEEN.md"
|
|
1732
|
+
pc_queen_global_data="{}"
|
|
1733
|
+
if [[ -f "$pc_queen_global_file" ]]; then
|
|
1734
|
+
pc_queen_global_cached=$(_cache_read "queen_global" "$pc_queen_global_file" 3600)
|
|
1735
|
+
if [[ "$pc_queen_global_cached" != "null" && -n "$pc_queen_global_cached" ]]; then
|
|
1736
|
+
pc_queen_global_data="$pc_queen_global_cached"
|
|
1737
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "cached" '.queen_global = $s' 2>/dev/null || echo '{}')
|
|
1738
|
+
else
|
|
1739
|
+
pc_queen_global_data=$(_extract_wisdom "$pc_queen_global_file" 2>/dev/null || echo '{}')
|
|
1740
|
+
if [[ -z "$pc_queen_global_data" || "$pc_queen_global_data" == "null" ]]; then
|
|
1741
|
+
pc_queen_global_data="{}"
|
|
1742
|
+
fi
|
|
1743
|
+
_cache_write "queen_global" "$pc_queen_global_file" "$pc_queen_global_data" 2>/dev/null || true
|
|
1744
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "fresh" '.queen_global = $s' 2>/dev/null || echo '{}')
|
|
1745
|
+
fi
|
|
1746
|
+
else
|
|
1747
|
+
pc_fallbacks+=("queen_global: no file found")
|
|
1748
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "missing" '.queen_global = $s' 2>/dev/null || echo '{}')
|
|
1749
|
+
fi
|
|
1750
|
+
|
|
1751
|
+
# === Section: queen (local) ===
|
|
1752
|
+
pc_queen_local_file="$AETHER_ROOT/.aether/QUEEN.md"
|
|
1753
|
+
pc_queen_local_data="{}"
|
|
1754
|
+
if [[ -f "$pc_queen_local_file" ]]; then
|
|
1755
|
+
pc_queen_local_cached=$(_cache_read "queen_local" "$pc_queen_local_file" 3600)
|
|
1756
|
+
if [[ "$pc_queen_local_cached" != "null" && -n "$pc_queen_local_cached" ]]; then
|
|
1757
|
+
pc_queen_local_data="$pc_queen_local_cached"
|
|
1758
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "cached" '.queen_local = $s' 2>/dev/null || echo '{}')
|
|
1759
|
+
else
|
|
1760
|
+
pc_queen_local_data=$(_extract_wisdom "$pc_queen_local_file" 2>/dev/null || echo '{}')
|
|
1761
|
+
if [[ -z "$pc_queen_local_data" || "$pc_queen_local_data" == "null" ]]; then
|
|
1762
|
+
pc_queen_local_data="{}"
|
|
1763
|
+
fi
|
|
1764
|
+
_cache_write "queen_local" "$pc_queen_local_file" "$pc_queen_local_data" 2>/dev/null || true
|
|
1765
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "fresh" '.queen_local = $s' 2>/dev/null || echo '{}')
|
|
1766
|
+
fi
|
|
1767
|
+
else
|
|
1768
|
+
pc_fallbacks+=("queen_local: no file found")
|
|
1769
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "missing" '.queen_local = $s' 2>/dev/null || echo '{}')
|
|
1770
|
+
fi
|
|
1771
|
+
|
|
1772
|
+
# === Section: user_preferences ===
|
|
1773
|
+
pc_user_prefs='[]'
|
|
1774
|
+
# Extract from global queen wisdom
|
|
1775
|
+
pc_up_raw=$(echo "$pc_queen_global_data" | jq -r '.user_prefs // ""' 2>/dev/null)
|
|
1776
|
+
if [[ -n "$pc_up_raw" && "$pc_up_raw" != "null" ]]; then
|
|
1777
|
+
pc_user_prefs=$(echo "$pc_up_raw" | jq -R -s 'split("\n") | map(select(length > 0))' 2>/dev/null || echo '[]')
|
|
1778
|
+
fi
|
|
1779
|
+
pc_up_local=$(echo "$pc_queen_local_data" | jq -r '.user_prefs // ""' 2>/dev/null)
|
|
1780
|
+
if [[ -n "$pc_up_local" && "$pc_up_local" != "null" ]]; then
|
|
1781
|
+
pc_user_prefs_local=$(echo "$pc_up_local" | jq -R -s 'split("\n") | map(select(length > 0))' 2>/dev/null || echo '[]')
|
|
1782
|
+
pc_user_prefs=$(echo "$pc_user_prefs" | jq --argjson local "$pc_user_prefs_local" '. + $local' 2>/dev/null || echo "$pc_user_prefs")
|
|
1783
|
+
fi
|
|
1784
|
+
|
|
1785
|
+
# === Section: signals ===
|
|
1786
|
+
pc_pher_file="${COLONY_DATA_DIR:-$DATA_DIR}/pheromones.json"
|
|
1787
|
+
pc_state_file="${COLONY_DATA_DIR:-$DATA_DIR}/COLONY_STATE.json"
|
|
1788
|
+
pc_signals_count=0
|
|
1789
|
+
pc_redirects='[]'
|
|
1790
|
+
pc_focus='[]'
|
|
1791
|
+
pc_feedback='[]'
|
|
1792
|
+
pc_instincts='[]'
|
|
1793
|
+
|
|
1794
|
+
if [[ -f "$pc_pher_file" ]]; then
|
|
1795
|
+
# Read and classify signals
|
|
1796
|
+
pc_signals_json=$(jq -r '.signals // []' "$pc_pher_file" 2>/dev/null || echo '[]')
|
|
1797
|
+
if [[ -n "$pc_signals_json" && "$pc_signals_json" != "null" ]]; then
|
|
1798
|
+
pc_signals_count=$(echo "$pc_signals_json" | jq 'length' 2>/dev/null || echo 0)
|
|
1799
|
+
pc_redirects=$(echo "$pc_signals_json" | jq '[.[] | select(.type == "REDIRECT")]' 2>/dev/null || echo '[]')
|
|
1800
|
+
pc_focus=$(echo "$pc_signals_json" | jq '[.[] | select(.type == "FOCUS")]' 2>/dev/null || echo '[]')
|
|
1801
|
+
pc_feedback=$(echo "$pc_signals_json" | jq '[.[] | select(.type == "FEEDBACK")]' 2>/dev/null || echo '[]')
|
|
1802
|
+
fi
|
|
1803
|
+
else
|
|
1804
|
+
pc_fallbacks+=("pheromones: no active signals")
|
|
1805
|
+
fi
|
|
1806
|
+
|
|
1807
|
+
# Read instincts from COLONY_STATE.json
|
|
1808
|
+
if [[ -f "$pc_state_file" ]]; then
|
|
1809
|
+
pc_instincts=$(jq -r '.memory.instincts // []' "$pc_state_file" 2>/dev/null || echo '[]')
|
|
1810
|
+
if [[ -z "$pc_instincts" || "$pc_instincts" == "null" ]]; then
|
|
1811
|
+
pc_instincts='[]'
|
|
1812
|
+
fi
|
|
1813
|
+
fi
|
|
1814
|
+
|
|
1815
|
+
# === Section: hive_wisdom ===
|
|
1816
|
+
pc_hive_data='[]'
|
|
1817
|
+
pc_hive_source="empty"
|
|
1818
|
+
pc_hive_file="$HOME/.aether/hive/wisdom.json"
|
|
1819
|
+
pc_eternal_file="$HOME/.aether/eternal/memory.json"
|
|
1820
|
+
|
|
1821
|
+
# Try hive first (via subcommand invocation for proper domain scoping)
|
|
1822
|
+
pc_hive_raw=$(bash "$SCRIPT_DIR/aether-utils.sh" hive-read --limit 5 --min-confidence 0.5 --format json 2>/dev/null || echo '')
|
|
1823
|
+
if [[ -n "$pc_hive_raw" ]]; then
|
|
1824
|
+
pc_hive_entries=$(echo "$pc_hive_raw" | jq -r '.result.entries // []' 2>/dev/null)
|
|
1825
|
+
if [[ -n "$pc_hive_entries" && "$pc_hive_entries" != "null" && "$pc_hive_entries" != "[]" ]]; then
|
|
1826
|
+
pc_hive_data="$pc_hive_entries"
|
|
1827
|
+
pc_hive_source="hive"
|
|
1828
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "fresh" '.hive = $s' 2>/dev/null || echo '{}')
|
|
1829
|
+
fi
|
|
1830
|
+
fi
|
|
1831
|
+
|
|
1832
|
+
# Fallback to eternal memory
|
|
1833
|
+
if [[ "$pc_hive_source" == "empty" && -f "$pc_eternal_file" ]]; then
|
|
1834
|
+
pc_eternal_cached=$(_cache_read "eternal" "$pc_eternal_file" 7200)
|
|
1835
|
+
if [[ "$pc_eternal_cached" != "null" && -n "$pc_eternal_cached" ]]; then
|
|
1836
|
+
pc_hive_data="$pc_eternal_cached"
|
|
1837
|
+
pc_hive_source="eternal"
|
|
1838
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "cached" '.hive = $s' 2>/dev/null || echo '{}')
|
|
1839
|
+
else
|
|
1840
|
+
pc_eternal_raw=$(jq -r '.entries // []' "$pc_eternal_file" 2>/dev/null || echo '[]')
|
|
1841
|
+
if [[ -n "$pc_eternal_raw" && "$pc_eternal_raw" != "null" && "$pc_eternal_raw" != "[]" ]]; then
|
|
1842
|
+
pc_hive_data="$pc_eternal_raw"
|
|
1843
|
+
pc_hive_source="eternal"
|
|
1844
|
+
_cache_write "eternal" "$pc_eternal_file" "$pc_eternal_raw" 2>/dev/null || true
|
|
1845
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "fresh" '.hive = $s' 2>/dev/null || echo '{}')
|
|
1846
|
+
fi
|
|
1847
|
+
fi
|
|
1848
|
+
fi
|
|
1849
|
+
|
|
1850
|
+
if [[ "$pc_hive_source" == "empty" ]]; then
|
|
1851
|
+
pc_fallbacks+=("hive_wisdom: no hive or eternal data")
|
|
1852
|
+
pc_cache_status=$(echo "$pc_cache_status" | jq --arg s "missing" '.hive = $s' 2>/dev/null || echo '{}')
|
|
1853
|
+
fi
|
|
1854
|
+
|
|
1855
|
+
# === Section: colony_state ===
|
|
1856
|
+
pc_cs_exists=false
|
|
1857
|
+
pc_cs_goal="No goal set"
|
|
1858
|
+
pc_cs_state="UNKNOWN"
|
|
1859
|
+
pc_cs_current_phase=0
|
|
1860
|
+
pc_cs_total_phases=0
|
|
1861
|
+
pc_cs_phase_name=""
|
|
1862
|
+
if [[ -f "$pc_state_file" ]]; then
|
|
1863
|
+
pc_cs_parsed=$(jq -r '{goal: .goal, state: .state, current_phase: .current_phase, total_phases: (.total_phases // (.plan.phases | length // 0)), phase_name: .phase_name}' "$pc_state_file" 2>/dev/null)
|
|
1864
|
+
if [[ -n "$pc_cs_parsed" && "$pc_cs_parsed" != "null" ]]; then
|
|
1865
|
+
pc_cs_exists=true
|
|
1866
|
+
pc_cs_goal=$(echo "$pc_cs_parsed" | jq -r '.goal // "No goal set"' 2>/dev/null)
|
|
1867
|
+
pc_cs_state=$(echo "$pc_cs_parsed" | jq -r '.state // "UNKNOWN"' 2>/dev/null)
|
|
1868
|
+
pc_cs_current_phase=$(echo "$pc_cs_parsed" | jq -r '.current_phase // 0' 2>/dev/null)
|
|
1869
|
+
pc_cs_total_phases=$(echo "$pc_cs_parsed" | jq -r '.total_phases // 0' 2>/dev/null)
|
|
1870
|
+
pc_cs_phase_name=$(echo "$pc_cs_parsed" | jq -r '.phase_name // ""' 2>/dev/null)
|
|
1871
|
+
else
|
|
1872
|
+
pc_fallbacks+=("colony_state: COLONY_STATE.json corrupt")
|
|
1873
|
+
fi
|
|
1874
|
+
else
|
|
1875
|
+
pc_fallbacks+=("colony_state: COLONY_STATE.json missing")
|
|
1876
|
+
fi
|
|
1877
|
+
|
|
1878
|
+
# === Section: blockers ===
|
|
1879
|
+
pc_flags_file="${COLONY_DATA_DIR:-$DATA_DIR}/flags.json"
|
|
1880
|
+
pc_blockers_count=0
|
|
1881
|
+
pc_blockers_items='[]'
|
|
1882
|
+
if [[ -f "$pc_flags_file" ]]; then
|
|
1883
|
+
pc_blockers_items=$(jq -r '[.flags // [] | .[] | select((.resolved // false) != true and ((.type // "") == "blocker" or (.severity // "") == "CRITICAL"))]' "$pc_flags_file" 2>/dev/null || echo '[]')
|
|
1884
|
+
pc_blockers_count=$(echo "$pc_blockers_items" | jq 'length' 2>/dev/null || echo 0)
|
|
1885
|
+
else
|
|
1886
|
+
pc_fallbacks+=("blockers: flags.json missing")
|
|
1887
|
+
fi
|
|
1888
|
+
|
|
1889
|
+
# === Section: decisions ===
|
|
1890
|
+
pc_decisions_count=0
|
|
1891
|
+
pc_decisions_items='[]'
|
|
1892
|
+
pc_context_file="$AETHER_ROOT/.aether/CONTEXT.md"
|
|
1893
|
+
if [[ -f "$pc_context_file" ]]; then
|
|
1894
|
+
# Extract decisions from CONTEXT.md if present
|
|
1895
|
+
pc_decisions_items=$(jq -r '.memory.decisions // []' "$pc_state_file" 2>/dev/null || echo '[]')
|
|
1896
|
+
if [[ -n "$pc_decisions_items" && "$pc_decisions_items" != "null" && "$pc_decisions_items" != "[]" ]]; then
|
|
1897
|
+
pc_decisions_count=$(echo "$pc_decisions_items" | jq 'length' 2>/dev/null || echo 0)
|
|
1898
|
+
fi
|
|
1899
|
+
elif [[ -f "$pc_state_file" ]]; then
|
|
1900
|
+
pc_decisions_items=$(jq -r '.memory.decisions // []' "$pc_state_file" 2>/dev/null || echo '[]')
|
|
1901
|
+
if [[ -n "$pc_decisions_items" && "$pc_decisions_items" != "null" ]]; then
|
|
1902
|
+
pc_decisions_count=$(echo "$pc_decisions_items" | jq 'length' 2>/dev/null || echo 0)
|
|
1903
|
+
fi
|
|
1904
|
+
fi
|
|
1905
|
+
|
|
1906
|
+
# === Section: rolling_summary ===
|
|
1907
|
+
pc_roll_file="${COLONY_DATA_DIR:-$DATA_DIR}/rolling-summary.log"
|
|
1908
|
+
pc_rolling=""
|
|
1909
|
+
if [[ -f "$pc_roll_file" ]]; then
|
|
1910
|
+
pc_rolling=$(tail -n 20 "$pc_roll_file" 2>/dev/null | head -20 || echo "")
|
|
1911
|
+
fi
|
|
1912
|
+
|
|
1913
|
+
# === Section: midden ===
|
|
1914
|
+
pc_midden_file="${COLONY_DATA_DIR:-$DATA_DIR}/midden/midden.json"
|
|
1915
|
+
pc_midden_count=0
|
|
1916
|
+
pc_midden_entries='[]'
|
|
1917
|
+
pc_midden_cross_pr='{}'
|
|
1918
|
+
|
|
1919
|
+
if [[ -f "$pc_midden_file" ]]; then
|
|
1920
|
+
# Bound: entries from last 7 days, cap at 10
|
|
1921
|
+
local now_epoch
|
|
1922
|
+
now_epoch=$(date +%s)
|
|
1923
|
+
local seven_days_ago=$(( now_epoch - 604800 ))
|
|
1924
|
+
pc_midden_entries=$(jq -r --argjson cutoff "$seven_days_ago" --argjson max 10 '
|
|
1925
|
+
[.entries // [] | .[] |
|
|
1926
|
+
# Parse occurred_at to epoch (best-effort)
|
|
1927
|
+
(.occurred_at // .timestamp // "") as $ts |
|
|
1928
|
+
($ts | split("T")) as $parts |
|
|
1929
|
+
if ($parts | length) > 1 then
|
|
1930
|
+
($parts[0] | split("-")) as $d |
|
|
1931
|
+
($parts[1] | rtrimstr("Z") | split(":")) as $t |
|
|
1932
|
+
(($d[0] // "0" | tonumber) - 1970) * 365 * 86400 +
|
|
1933
|
+
(($d[1] // "0" | tonumber) - 1) * 30 * 86400 +
|
|
1934
|
+
(($d[2] // "0" | tonumber) - 1) * 86400 +
|
|
1935
|
+
(($t[0] // "0" | tonumber) * 3600) +
|
|
1936
|
+
(($t[1] // "0" | tonumber) * 60) +
|
|
1937
|
+
(($t[2] // "0" | rtrimstr("Z") | tonumber) // 0) as $epoch |
|
|
1938
|
+
. + {_epoch: $epoch}
|
|
1939
|
+
else . + {_epoch: 0} end
|
|
1940
|
+
] | sort_by(-._epoch) | .[:$max] |
|
|
1941
|
+
map(del(._epoch) | .description = ((.description // "")[0:160]))
|
|
1942
|
+
' "$pc_midden_file" 2>/dev/null || echo '[]')
|
|
1943
|
+
pc_midden_count=$(echo "$pc_midden_entries" | jq 'length' 2>/dev/null || echo 0)
|
|
1944
|
+
else
|
|
1945
|
+
pc_fallbacks+=("midden: midden.json missing")
|
|
1946
|
+
fi
|
|
1947
|
+
|
|
1948
|
+
# === Section: context_capsule ===
|
|
1949
|
+
pc_capsule_data='{}'
|
|
1950
|
+
pc_capsule_raw=$(bash "$SCRIPT_DIR/aether-utils.sh" context-capsule --json 2>/dev/null || echo '')
|
|
1951
|
+
if [[ -n "$pc_capsule_raw" ]]; then
|
|
1952
|
+
pc_capsule_data=$(echo "$pc_capsule_raw" | jq -r '.result // . // {}' 2>/dev/null || echo '{}')
|
|
1953
|
+
fi
|
|
1954
|
+
|
|
1955
|
+
# === Section: phase_learnings ===
|
|
1956
|
+
pc_learnings=""
|
|
1957
|
+
if [[ -f "$pc_state_file" ]]; then
|
|
1958
|
+
pc_learnings=$(jq -r '.memory.phase_learnings // [] | map(if type == "object" then (.summary // .description // tostring) else tostring end) | .[]' "$pc_state_file" 2>/dev/null | head -20 || echo "")
|
|
1959
|
+
fi
|
|
1960
|
+
|
|
1961
|
+
# === Build prompt_section (text version) ===
|
|
1962
|
+
pc_sec_queen_global=""
|
|
1963
|
+
pc_sec_queen_local=""
|
|
1964
|
+
pc_sec_user_prefs=""
|
|
1965
|
+
pc_sec_hive=""
|
|
1966
|
+
pc_sec_capsule=""
|
|
1967
|
+
pc_sec_learnings=""
|
|
1968
|
+
pc_sec_decisions=""
|
|
1969
|
+
pc_sec_blockers=""
|
|
1970
|
+
pc_sec_rolling=""
|
|
1971
|
+
pc_sec_signals=""
|
|
1972
|
+
|
|
1973
|
+
# QUEEN global section
|
|
1974
|
+
local _pc_qg_raw=""
|
|
1975
|
+
if [[ -f "$pc_queen_global_file" ]]; then
|
|
1976
|
+
_pc_qg_raw=$(echo "$pc_queen_global_data" | jq -r 'to_entries | map("\(.key): \(.value)") | .[]' 2>/dev/null)
|
|
1977
|
+
fi
|
|
1978
|
+
if [[ -n "$_pc_qg_raw" ]]; then
|
|
1979
|
+
pc_sec_queen_global=$'\n'"--- QUEEN WISDOM (Global) ---"$'\n'"$_pc_qg_raw"$'\n'
|
|
1980
|
+
fi
|
|
1981
|
+
|
|
1982
|
+
# QUEEN local section
|
|
1983
|
+
local _pc_ql_raw=""
|
|
1984
|
+
if [[ -f "$pc_queen_local_file" ]]; then
|
|
1985
|
+
_pc_ql_raw=$(echo "$pc_queen_local_data" | jq -r 'to_entries | map("\(.key): \(.value)") | .[]' 2>/dev/null)
|
|
1986
|
+
fi
|
|
1987
|
+
if [[ -n "$_pc_ql_raw" ]]; then
|
|
1988
|
+
pc_sec_queen_local=$'\n'"--- QUEEN WISDOM (Local) ---"$'\n'"$_pc_ql_raw"$'\n'
|
|
1989
|
+
fi
|
|
1990
|
+
|
|
1991
|
+
# User preferences
|
|
1992
|
+
if [[ "$(echo "$pc_user_prefs" | jq 'length' 2>/dev/null)" -gt 0 ]]; then
|
|
1993
|
+
pc_sec_user_prefs=$'\n'"--- USER PREFERENCES ---"$'\n'
|
|
1994
|
+
pc_sec_user_prefs+=$(echo "$pc_user_prefs" | jq -r '.[]' 2>/dev/null | while IFS= read -r line; do echo "- $line"; done)
|
|
1995
|
+
pc_sec_user_prefs+=$'\n'
|
|
1996
|
+
fi
|
|
1997
|
+
|
|
1998
|
+
# Signals section
|
|
1999
|
+
if [[ "$pc_signals_count" -gt 0 ]]; then
|
|
2000
|
+
pc_sec_signals=$'\n'"--- ACTIVE SIGNALS (Colony Guidance) ---"$'\n'
|
|
2001
|
+
local _pc_redirects_text=""
|
|
2002
|
+
_pc_redirects_text=$(echo "$pc_redirects" | jq -r '.[] | "REDIRECT (HARD CONSTRAINT): " + (.content.text // (.content | if type == "string" then . else "" end))' 2>/dev/null)
|
|
2003
|
+
if [[ -n "$_pc_redirects_text" ]]; then
|
|
2004
|
+
pc_sec_signals+=$'\n'"REDIRECT (HARD CONSTRAINTS):"$'\n'
|
|
2005
|
+
while IFS= read -r line; do [[ -n "$line" ]] && pc_sec_signals+="- $line"$'\n'; done <<< "$_pc_redirects_text"
|
|
2006
|
+
fi
|
|
2007
|
+
local _pc_focus_text=""
|
|
2008
|
+
_pc_focus_text=$(echo "$pc_focus" | jq -r '.[] | "FOCUS: " + (.content.text // (.content | if type == "string" then . else "" end))' 2>/dev/null)
|
|
2009
|
+
if [[ -n "$_pc_focus_text" ]]; then
|
|
2010
|
+
pc_sec_signals+=$'\n'"FOCUS (Active Guidance):"$'\n'
|
|
2011
|
+
while IFS= read -r line; do [[ -n "$line" ]] && pc_sec_signals+="- $line"$'\n'; done <<< "$_pc_focus_text"
|
|
2012
|
+
fi
|
|
2013
|
+
local _pc_feedback_text=""
|
|
2014
|
+
_pc_feedback_text=$(echo "$pc_feedback" | jq -r '.[] | "FEEDBACK: " + (.content.text // (.content | if type == "string" then . else "" end))' 2>/dev/null)
|
|
2015
|
+
if [[ -n "$_pc_feedback_text" ]]; then
|
|
2016
|
+
pc_sec_signals+=$'\n'"FEEDBACK (Adjustments):"$'\n'
|
|
2017
|
+
while IFS= read -r line; do [[ -n "$line" ]] && pc_sec_signals+="- $line"$'\n'; done <<< "$_pc_feedback_text"
|
|
2018
|
+
fi
|
|
2019
|
+
pc_sec_signals+=$'\n'"--- END SIGNALS ---"$'\n'
|
|
2020
|
+
fi
|
|
2021
|
+
|
|
2022
|
+
# Hive wisdom
|
|
2023
|
+
local _pc_hive_count=0
|
|
2024
|
+
_pc_hive_count=$(echo "$pc_hive_data" | jq 'length' 2>/dev/null || echo 0)
|
|
2025
|
+
if [[ "$_pc_hive_count" -gt 0 ]]; then
|
|
2026
|
+
pc_sec_hive=$'\n'"--- HIVE WISDOM (Cross-Colony Patterns) ---"$'\n'
|
|
2027
|
+
pc_sec_hive+=$(echo "$pc_hive_data" | jq -r '.[] | "- " + (.wisdom // .text // (. | tostring))' 2>/dev/null | head -10)
|
|
2028
|
+
pc_sec_hive+=$'\n'
|
|
2029
|
+
fi
|
|
2030
|
+
|
|
2031
|
+
# Context capsule
|
|
2032
|
+
local _pc_capsule_text=""
|
|
2033
|
+
_pc_capsule_text=$(echo "$pc_capsule_data" | jq -r '.prompt_section // ""' 2>/dev/null)
|
|
2034
|
+
if [[ -n "$_pc_capsule_text" ]]; then
|
|
2035
|
+
pc_sec_capsule=$'\n'"$_pc_capsule_text"$'\n'
|
|
2036
|
+
fi
|
|
2037
|
+
|
|
2038
|
+
# Phase learnings
|
|
2039
|
+
if [[ -n "$pc_learnings" ]]; then
|
|
2040
|
+
pc_sec_learnings=$'\n'"--- PHASE LEARNINGS ---"$'\n'"$pc_learnings"$'\n'
|
|
2041
|
+
fi
|
|
2042
|
+
|
|
2043
|
+
# Decisions
|
|
2044
|
+
if [[ "$pc_decisions_count" -gt 0 ]]; then
|
|
2045
|
+
pc_sec_decisions=$'\n'"--- KEY DECISIONS ---"$'\n'
|
|
2046
|
+
pc_sec_decisions+=$(echo "$pc_decisions_items" | jq -r '.[] | if type == "object" then "- " + (.decision // .summary // .description // tostring) else "- " + tostring end' 2>/dev/null | head -10)
|
|
2047
|
+
pc_sec_decisions+=$'\n'
|
|
2048
|
+
fi
|
|
2049
|
+
|
|
2050
|
+
# Blockers
|
|
2051
|
+
if [[ "$pc_blockers_count" -gt 0 ]]; then
|
|
2052
|
+
pc_sec_blockers=$'\n'"--- BLOCKERS (CRITICAL) ---"$'\n'
|
|
2053
|
+
pc_sec_blockers+=$(echo "$pc_blockers_items" | jq -r '.[] | "- " + (.title // .description // tostring)' 2>/dev/null | head -10)
|
|
2054
|
+
pc_sec_blockers+=$'\n'
|
|
2055
|
+
fi
|
|
2056
|
+
|
|
2057
|
+
# Rolling summary
|
|
2058
|
+
if [[ -n "$pc_rolling" ]]; then
|
|
2059
|
+
pc_sec_rolling=$'\n'"--- ROLLING SUMMARY ---"$'\n'"$pc_rolling"$'\n'
|
|
2060
|
+
fi
|
|
2061
|
+
|
|
2062
|
+
# === Budget enforcement ===
|
|
2063
|
+
_budget_enforce "pc_"
|
|
2064
|
+
|
|
2065
|
+
# Trim notification
|
|
2066
|
+
local pc_trimmed_sections=""
|
|
2067
|
+
if [[ -n "${pc_budget_trimmed_list:-}" ]]; then
|
|
2068
|
+
pc_trimmed_sections=$(echo "$pc_budget_trimmed_list" | tr ',' ', ')
|
|
2069
|
+
fi
|
|
2070
|
+
|
|
2071
|
+
# === Build output JSON ===
|
|
2072
|
+
# Build fallbacks JSON array
|
|
2073
|
+
local pc_fallbacks_json='[]'
|
|
2074
|
+
local fb
|
|
2075
|
+
for fb in ${pc_fallbacks[@]+"${pc_fallbacks[@]}"}; do
|
|
2076
|
+
pc_fallbacks_json=$(echo "$pc_fallbacks_json" | jq --arg f "$fb" '. + [$f]' 2>/dev/null || echo '[]')
|
|
2077
|
+
done
|
|
2078
|
+
|
|
2079
|
+
# Build warnings JSON array
|
|
2080
|
+
local pc_warnings_json='[]'
|
|
2081
|
+
local w
|
|
2082
|
+
for w in ${pc_warnings[@]+"${pc_warnings[@]}"}; do
|
|
2083
|
+
pc_warnings_json=$(echo "$pc_warnings_json" | jq --arg f "$w" '. + [$f]' 2>/dev/null || echo '[]')
|
|
2084
|
+
done
|
|
2085
|
+
|
|
2086
|
+
# Trimmed sections JSON array
|
|
2087
|
+
local pc_trimmed_json='[]'
|
|
2088
|
+
if [[ -n "${pc_budget_trimmed_list:-}" ]]; then
|
|
2089
|
+
pc_trimmed_json=$(echo "$pc_budget_trimmed_list" | jq -R 'split(",")' 2>/dev/null || echo '[]')
|
|
2090
|
+
fi
|
|
2091
|
+
|
|
2092
|
+
# Escape prompt_section for JSON
|
|
2093
|
+
local pc_prompt_json
|
|
2094
|
+
pc_prompt_json=$(printf '%s' "$pc_final_prompt" | jq -Rs '.' 2>/dev/null || echo '""')
|
|
2095
|
+
|
|
2096
|
+
# Colony state JSON
|
|
2097
|
+
local pc_colony_state_json
|
|
2098
|
+
pc_colony_state_json=$(jq -n \
|
|
2099
|
+
--argjson exists "$pc_cs_exists" \
|
|
2100
|
+
--arg goal "$pc_cs_goal" \
|
|
2101
|
+
--arg state "$pc_cs_state" \
|
|
2102
|
+
--argjson current_phase "$pc_cs_current_phase" \
|
|
2103
|
+
--argjson total_phases "$pc_cs_total_phases" \
|
|
2104
|
+
--arg phase_name "$pc_cs_phase_name" \
|
|
2105
|
+
'{exists: $exists, goal: $goal, state: $state, current_phase: $current_phase, total_phases: $total_phases, phase_name: $phase_name}')
|
|
2106
|
+
|
|
2107
|
+
# Build result
|
|
2108
|
+
local pc_result
|
|
2109
|
+
pc_result=$(jq -n \
|
|
2110
|
+
--arg schema "pr-context-v1" \
|
|
2111
|
+
--arg generated_at "$pc_generated_at" \
|
|
2112
|
+
--arg branch "$pc_branch" \
|
|
2113
|
+
--argjson cache_status "$pc_cache_status" \
|
|
2114
|
+
--argjson queen_global "$pc_queen_global_data" \
|
|
2115
|
+
--argjson queen_local "$pc_queen_local_data" \
|
|
2116
|
+
--argjson combined_prefs "$pc_user_prefs" \
|
|
2117
|
+
--argjson signals_count "$pc_signals_count" \
|
|
2118
|
+
--argjson redirects "$pc_redirects" \
|
|
2119
|
+
--argjson focus "$pc_focus" \
|
|
2120
|
+
--argjson feedback "$pc_feedback" \
|
|
2121
|
+
--argjson instincts "$pc_instincts" \
|
|
2122
|
+
--arg hive_source "$pc_hive_source" \
|
|
2123
|
+
--argjson hive_count "$(echo "$pc_hive_data" | jq 'length' 2>/dev/null || echo 0)" \
|
|
2124
|
+
--argjson hive_entries "$pc_hive_data" \
|
|
2125
|
+
--argjson colony_state "$pc_colony_state_json" \
|
|
2126
|
+
--argjson blockers_count "$pc_blockers_count" \
|
|
2127
|
+
--argjson blockers_items "$pc_blockers_items" \
|
|
2128
|
+
--argjson decisions_count "$pc_decisions_count" \
|
|
2129
|
+
--argjson decisions_items "$pc_decisions_items" \
|
|
2130
|
+
--argjson midden_count "$pc_midden_count" \
|
|
2131
|
+
--argjson midden_entries "$pc_midden_entries" \
|
|
2132
|
+
--argjson midden_cross_pr "$pc_midden_cross_pr" \
|
|
2133
|
+
--argjson prompt_section "$pc_prompt_json" \
|
|
2134
|
+
--argjson char_count "${#pc_final_prompt}" \
|
|
2135
|
+
--argjson budget "$pc_max_chars" \
|
|
2136
|
+
--argjson trimmed_sections "$pc_trimmed_json" \
|
|
2137
|
+
--argjson warnings "$pc_warnings_json" \
|
|
2138
|
+
--argjson fallbacks_used "$pc_fallbacks_json" \
|
|
2139
|
+
'{
|
|
2140
|
+
schema: $schema,
|
|
2141
|
+
generated_at: $generated_at,
|
|
2142
|
+
branch: $branch,
|
|
2143
|
+
cache_status: $cache_status,
|
|
2144
|
+
queen: {global: $queen_global, local: $queen_local, combined_prefs: $combined_prefs},
|
|
2145
|
+
signals: {count: $signals_count, redirects: $redirects, focus: $focus, feedback: $feedback, instincts: $instincts},
|
|
2146
|
+
hive: {source: $hive_source, count: $hive_count, entries: $hive_entries},
|
|
2147
|
+
colony_state: $colony_state,
|
|
2148
|
+
blockers: {count: $blockers_count, items: $blockers_items},
|
|
2149
|
+
decisions: {count: $decisions_count, items: $decisions_items},
|
|
2150
|
+
midden: {count: $midden_count, entries: $midden_entries, cross_pr_analysis: $midden_cross_pr},
|
|
2151
|
+
prompt_section: $prompt_section,
|
|
2152
|
+
char_count: $char_count,
|
|
2153
|
+
budget: $budget,
|
|
2154
|
+
trimmed_sections: $trimmed_sections,
|
|
2155
|
+
warnings: $warnings,
|
|
2156
|
+
fallbacks_used: $fallbacks_used
|
|
2157
|
+
}')
|
|
2158
|
+
|
|
2159
|
+
# Validate result
|
|
2160
|
+
if [[ -z "$pc_result" ]] || ! echo "$pc_result" | jq -e . >/dev/null 2>&1; then
|
|
2161
|
+
json_err "$E_JSON_INVALID" \
|
|
2162
|
+
"Couldn't assemble pr-context output" \
|
|
2163
|
+
'{"error":"assembly_failed"}'
|
|
2164
|
+
fi
|
|
2165
|
+
|
|
2166
|
+
json_ok "$pc_result"
|
|
2167
|
+
}
|
|
2168
|
+
|
|
1555
2169
|
# ============================================================================
|
|
1556
2170
|
# _pheromone_expire
|
|
1557
2171
|
# Archive expired pheromone signals to midden
|
|
@@ -2027,3 +2641,657 @@ source "$SCRIPT_DIR/exchange/pheromone-xml.sh"
|
|
|
2027
2641
|
xml-pheromone-validate "$pvx_xml" "$pvx_xsd"
|
|
2028
2642
|
}
|
|
2029
2643
|
|
|
2644
|
+
# ============================================================================
|
|
2645
|
+
# _pheromone_snapshot_inject
|
|
2646
|
+
# Inject canonical signals from main into the current branch
|
|
2647
|
+
# ============================================================================
|
|
2648
|
+
_pheromone_snapshot_inject() {
|
|
2649
|
+
# Inject main's injectable signals into the current branch's pheromones.json
|
|
2650
|
+
# Usage: pheromone-snapshot-inject --from-branch BRANCH --from-commit SHA
|
|
2651
|
+
# --from-branch: source branch (typically "main")
|
|
2652
|
+
# --from-commit: commit SHA of the source branch at injection time
|
|
2653
|
+
# Returns: JSON with injected_count, skipped_count, snapshot metadata
|
|
2654
|
+
|
|
2655
|
+
psi_from_branch="main"
|
|
2656
|
+
psi_from_commit=""
|
|
2657
|
+
|
|
2658
|
+
while [[ $# -gt 0 ]]; do
|
|
2659
|
+
case "$1" in
|
|
2660
|
+
--from-branch) shift; psi_from_branch="${1:-main}" ;;
|
|
2661
|
+
--from-commit) shift; psi_from_commit="${1:-}" ;;
|
|
2662
|
+
*) shift ;;
|
|
2663
|
+
esac
|
|
2664
|
+
done
|
|
2665
|
+
|
|
2666
|
+
if [[ -z "$psi_from_commit" ]]; then
|
|
2667
|
+
json_err "$E_VALIDATION_FAILED" "pheromone-snapshot-inject requires --from-commit argument"
|
|
2668
|
+
fi
|
|
2669
|
+
|
|
2670
|
+
psi_file="$COLONY_DATA_DIR/pheromones.json"
|
|
2671
|
+
|
|
2672
|
+
# Edge case: no pheromones.json on main -- no-op
|
|
2673
|
+
if [[ ! -f "$psi_file" ]]; then
|
|
2674
|
+
json_ok "$(jq -n --arg branch "$psi_from_branch" --arg commit "$psi_from_commit" \
|
|
2675
|
+
'{snapshot_from_branch: $branch, snapshot_from_commit: $commit, injected_count: 0, skipped_count: 0}')"
|
|
2676
|
+
return 0
|
|
2677
|
+
fi
|
|
2678
|
+
|
|
2679
|
+
psi_now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2680
|
+
|
|
2681
|
+
# Read active signals and filter injectable ones
|
|
2682
|
+
# Filter rule: REDIRECT (any source) OR (user source AND type IN (FOCUS, FEEDBACK))
|
|
2683
|
+
psi_filtered=$(jq -c --arg now "$psi_now_iso" '
|
|
2684
|
+
def to_epoch(ts):
|
|
2685
|
+
if ts == null or ts == "" or ts == "phase_end" then null
|
|
2686
|
+
else
|
|
2687
|
+
(ts | split("T")) as $parts |
|
|
2688
|
+
($parts[0] | split("-")) as $d |
|
|
2689
|
+
($parts[1] | rtrimstr("Z") | split(":")) as $t |
|
|
2690
|
+
(($d[0] | tonumber) - 1970) * 365 * 86400 +
|
|
2691
|
+
(($d[1] | tonumber) - 1) * 30 * 86400 +
|
|
2692
|
+
(($d[2] | tonumber) - 1) * 86400 +
|
|
2693
|
+
($t[0] | tonumber) * 3600 +
|
|
2694
|
+
($t[1] | tonumber) * 60 +
|
|
2695
|
+
($t[2] | rtrimstr("Z") | tonumber)
|
|
2696
|
+
end;
|
|
2697
|
+
|
|
2698
|
+
(to_epoch($now)) as $now_epoch |
|
|
2699
|
+
|
|
2700
|
+
.signals | map(select(.active == true)) |
|
|
2701
|
+
map(
|
|
2702
|
+
# Check expiry
|
|
2703
|
+
(to_epoch(.expires_at)) as $exp_epoch |
|
|
2704
|
+
select(if $exp_epoch != null then $exp_epoch > $now_epoch else true end) |
|
|
2705
|
+
# Apply injection filter
|
|
2706
|
+
select(
|
|
2707
|
+
.type == "REDIRECT"
|
|
2708
|
+
or
|
|
2709
|
+
(.source == "user" and (.type == "FOCUS" or .type == "FEEDBACK"))
|
|
2710
|
+
)
|
|
2711
|
+
)
|
|
2712
|
+
' "$psi_file" 2>/dev/null || echo "[]")
|
|
2713
|
+
|
|
2714
|
+
if [[ -z "$psi_filtered" || "$psi_filtered" == "null" ]]; then
|
|
2715
|
+
psi_filtered="[]"
|
|
2716
|
+
fi
|
|
2717
|
+
|
|
2718
|
+
psi_injected_ids=()
|
|
2719
|
+
psi_skipped_ids=()
|
|
2720
|
+
psi_injected_details="[]"
|
|
2721
|
+
psi_skipped_details="[]"
|
|
2722
|
+
|
|
2723
|
+
# Count total active signals for skip tracking
|
|
2724
|
+
psi_total_active=$(jq '[.signals[] | select(.active == true)] | length' "$psi_file" 2>/dev/null || echo "0")
|
|
2725
|
+
psi_inject_count=$(echo "$psi_filtered" | jq 'length')
|
|
2726
|
+
psi_skip_count=$((psi_total_active - psi_inject_count))
|
|
2727
|
+
|
|
2728
|
+
# Build skipped reasons
|
|
2729
|
+
psi_skip_reasons="[]"
|
|
2730
|
+
if [[ "$psi_skip_count" -gt 0 ]]; then
|
|
2731
|
+
psi_skip_reasons=$(jq -c --arg now "$psi_now_iso" '
|
|
2732
|
+
def to_epoch(ts):
|
|
2733
|
+
if ts == null or ts == "" or ts == "phase_end" then null
|
|
2734
|
+
else
|
|
2735
|
+
(ts | split("T")) as $parts |
|
|
2736
|
+
($parts[0] | split("-")) as $d |
|
|
2737
|
+
($parts[1] | rtrimstr("Z") | split(":")) as $t |
|
|
2738
|
+
(($d[0] | tonumber) - 1970) * 365 * 86400 +
|
|
2739
|
+
(($d[1] | tonumber) - 1) * 30 * 86400 +
|
|
2740
|
+
(($d[2] | tonumber) - 1) * 86400 +
|
|
2741
|
+
($t[0] | tonumber) * 3600 +
|
|
2742
|
+
($t[1] | tonumber) * 60 +
|
|
2743
|
+
($t[2] | rtrimstr("Z") | tonumber)
|
|
2744
|
+
end;
|
|
2745
|
+
|
|
2746
|
+
(to_epoch($now)) as $now_epoch |
|
|
2747
|
+
|
|
2748
|
+
.signals | map(select(.active == true)) |
|
|
2749
|
+
map(
|
|
2750
|
+
(to_epoch(.expires_at)) as $exp_epoch |
|
|
2751
|
+
select(if $exp_epoch != null then $exp_epoch > $now_epoch else true end) |
|
|
2752
|
+
select(
|
|
2753
|
+
.type != "REDIRECT"
|
|
2754
|
+
and
|
|
2755
|
+
(.source != "user" or (.type != "FOCUS" and .type != "FEEDBACK"))
|
|
2756
|
+
)
|
|
2757
|
+
) | map({
|
|
2758
|
+
original_id: .id,
|
|
2759
|
+
type: .type,
|
|
2760
|
+
source: .source,
|
|
2761
|
+
reason: (if .type == "FOCUS" or .type == "FEEDBACK" then "worker/system-sourced \(.type) excluded from injection" else "signal type \(.type) excluded from injection" end)
|
|
2762
|
+
})
|
|
2763
|
+
' "$psi_file" 2>/dev/null || echo "[]")
|
|
2764
|
+
fi
|
|
2765
|
+
|
|
2766
|
+
# Inject each signal via _pheromone_write (reuses content_hash dedup)
|
|
2767
|
+
psi_injected_count=0
|
|
2768
|
+
if [[ "$psi_inject_count" -gt 0 ]]; then
|
|
2769
|
+
psi_injected_details=$(echo "$psi_filtered" | jq -c --arg now "$psi_now_iso" '
|
|
2770
|
+
map({
|
|
2771
|
+
original_id: .id,
|
|
2772
|
+
type: .type,
|
|
2773
|
+
content_hash: .content_hash,
|
|
2774
|
+
strength: .strength,
|
|
2775
|
+
source: .source,
|
|
2776
|
+
action: "injected"
|
|
2777
|
+
})
|
|
2778
|
+
')
|
|
2779
|
+
|
|
2780
|
+
# Compute TTL from expires_at for each signal
|
|
2781
|
+
echo "$psi_filtered" | jq -c '.[]' | while IFS= read -r sig; do
|
|
2782
|
+
local_sig_type=$(echo "$sig" | jq -r '.type')
|
|
2783
|
+
local_sig_content=$(echo "$sig" | jq -r '.content.text // .content // ""')
|
|
2784
|
+
local_sig_strength=$(echo "$sig" | jq -r '.strength')
|
|
2785
|
+
local_sig_source=$(echo "$sig" | jq -r '.source')
|
|
2786
|
+
local_sig_expires=$(echo "$sig" | jq -r '.expires_at')
|
|
2787
|
+
|
|
2788
|
+
# Compute TTL from remaining time
|
|
2789
|
+
local_sig_ttl="phase_end"
|
|
2790
|
+
if [[ "$local_sig_expires" != "phase_end" && -n "$local_sig_expires" ]]; then
|
|
2791
|
+
# Parse expires_at epoch using jq's to_epoch (same logic as _pheromone_write)
|
|
2792
|
+
local_exp_epoch=$(echo "$sig" | jq --arg now "$psi_now_iso" '
|
|
2793
|
+
def to_epoch(ts):
|
|
2794
|
+
(ts | split("T")) as $parts |
|
|
2795
|
+
($parts[0] | split("-")) as $d |
|
|
2796
|
+
($parts[1] | rtrimstr("Z") | split(":")) as $t |
|
|
2797
|
+
(($d[0] | tonumber) - 1970) * 365 * 86400 +
|
|
2798
|
+
(($d[1] | tonumber) - 1) * 30 * 86400 +
|
|
2799
|
+
(($d[2] | tonumber) - 1) * 86400 +
|
|
2800
|
+
($t[0] | tonumber) * 3600 +
|
|
2801
|
+
($t[1] | tonumber) * 60 +
|
|
2802
|
+
($t[2] | rtrimstr("Z") | tonumber)
|
|
2803
|
+
end;
|
|
2804
|
+
to_epoch(.expires_at)
|
|
2805
|
+
' 2>/dev/null || echo "0")
|
|
2806
|
+
|
|
2807
|
+
local_now_epoch=$(date +%s)
|
|
2808
|
+
local_remaining=$(( local_exp_epoch - local_now_epoch ))
|
|
2809
|
+
|
|
2810
|
+
if [[ "$local_remaining" -gt 0 ]]; then
|
|
2811
|
+
# Convert to hours/days for TTL
|
|
2812
|
+
if [[ "$local_remaining" -ge 86400 ]]; then
|
|
2813
|
+
local_sig_ttl="$(( local_remaining / 86400 ))d"
|
|
2814
|
+
else
|
|
2815
|
+
local_sig_ttl="$(( local_remaining / 3600 ))h"
|
|
2816
|
+
fi
|
|
2817
|
+
fi
|
|
2818
|
+
fi
|
|
2819
|
+
|
|
2820
|
+
_pheromone_write "$local_sig_type" "$local_sig_content" \
|
|
2821
|
+
--strength "$local_sig_strength" \
|
|
2822
|
+
--ttl "$local_sig_ttl" \
|
|
2823
|
+
--source "$local_sig_source" \
|
|
2824
|
+
--reason "Injected from $psi_from_branch branch (snapshot)" \
|
|
2825
|
+
>/dev/null 2>&1 || true
|
|
2826
|
+
done
|
|
2827
|
+
|
|
2828
|
+
psi_injected_count="$psi_inject_count"
|
|
2829
|
+
fi
|
|
2830
|
+
|
|
2831
|
+
# Write snapshot metadata
|
|
2832
|
+
psi_snapshot_file="$COLONY_DATA_DIR/pheromone-snapshot.json"
|
|
2833
|
+
psi_snapshot=$(jq -n \
|
|
2834
|
+
--arg schema "pheromone-snapshot-v1" \
|
|
2835
|
+
--arg branch "$psi_from_branch" \
|
|
2836
|
+
--arg commit "$psi_from_commit" \
|
|
2837
|
+
--arg at "$psi_now_iso" \
|
|
2838
|
+
--argjson injected "$psi_injected_details" \
|
|
2839
|
+
--argjson skipped "$psi_skip_reasons" \
|
|
2840
|
+
--argjson injected_count "$psi_injected_count" \
|
|
2841
|
+
--argjson skipped_count "$psi_skip_count" \
|
|
2842
|
+
'{
|
|
2843
|
+
schema: $schema,
|
|
2844
|
+
snapshot_from_branch: $branch,
|
|
2845
|
+
snapshot_from_commit: $commit,
|
|
2846
|
+
snapshot_at: $at,
|
|
2847
|
+
injected: $injected,
|
|
2848
|
+
skipped: $skipped,
|
|
2849
|
+
injected_count: $injected_count,
|
|
2850
|
+
skipped_count: $skipped_count
|
|
2851
|
+
}')
|
|
2852
|
+
|
|
2853
|
+
atomic_write "$psi_snapshot_file" "$psi_snapshot" 2>/dev/null || {
|
|
2854
|
+
_aether_log_error "Could not write pheromone snapshot metadata"
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
json_ok "$(jq -n \
|
|
2858
|
+
--arg branch "$psi_from_branch" \
|
|
2859
|
+
--arg commit "$psi_from_commit" \
|
|
2860
|
+
--argjson injected_count "$psi_injected_count" \
|
|
2861
|
+
--argjson skipped_count "$psi_skip_count" \
|
|
2862
|
+
'{
|
|
2863
|
+
snapshot_from_branch: $branch,
|
|
2864
|
+
snapshot_from_commit: $commit,
|
|
2865
|
+
injected_count: $injected_count,
|
|
2866
|
+
skipped_count: $skipped_count
|
|
2867
|
+
}')"
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
# ============================================================================
|
|
2871
|
+
# _pheromone_export_branch
|
|
2872
|
+
# Export branch signals for merge-back (pre-merge step)
|
|
2873
|
+
# ============================================================================
|
|
2874
|
+
_pheromone_export_branch() {
|
|
2875
|
+
# Export branch's eligible signals for merge-back
|
|
2876
|
+
# Usage: pheromone-export-branch
|
|
2877
|
+
# Returns: JSON with eligible_count, ineligible_count, total_signals
|
|
2878
|
+
# Side effect: writes .aether/exchange/pheromone-branch-export.json
|
|
2879
|
+
|
|
2880
|
+
peb_file="$COLONY_DATA_DIR/pheromones.json"
|
|
2881
|
+
|
|
2882
|
+
if [[ ! -f "$peb_file" ]]; then
|
|
2883
|
+
json_err "$E_FILE_NOT_FOUND" "pheromones.json not found. No signals to export."
|
|
2884
|
+
fi
|
|
2885
|
+
|
|
2886
|
+
peb_now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2887
|
+
|
|
2888
|
+
# Get current branch name and commit
|
|
2889
|
+
peb_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
2890
|
+
peb_commit=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
2891
|
+
|
|
2892
|
+
# Read all active signals and determine eligibility
|
|
2893
|
+
peb_all_signals=$(jq -c --arg now "$peb_now_iso" '
|
|
2894
|
+
def to_epoch(ts):
|
|
2895
|
+
if ts == null or ts == "" or ts == "phase_end" then null
|
|
2896
|
+
else
|
|
2897
|
+
(ts | split("T")) as $parts |
|
|
2898
|
+
($parts[0] | split("-")) as $d |
|
|
2899
|
+
($parts[1] | rtrimstr("Z") | split(":")) as $t |
|
|
2900
|
+
(($d[0] | tonumber) - 1970) * 365 * 86400 +
|
|
2901
|
+
(($d[1] | tonumber) - 1) * 30 * 86400 +
|
|
2902
|
+
(($d[2] | tonumber) - 1) * 86400 +
|
|
2903
|
+
($t[0] | tonumber) * 3600 +
|
|
2904
|
+
($t[1] | tonumber) * 60 +
|
|
2905
|
+
($t[2] | rtrimstr("Z") | tonumber)
|
|
2906
|
+
end;
|
|
2907
|
+
|
|
2908
|
+
(to_epoch($now)) as $now_epoch |
|
|
2909
|
+
|
|
2910
|
+
.signals | map(select(.active == true)) |
|
|
2911
|
+
map(
|
|
2912
|
+
(to_epoch(.expires_at)) as $exp_epoch |
|
|
2913
|
+
select(if $exp_epoch != null then $exp_epoch > $now_epoch else true end) |
|
|
2914
|
+
. + {
|
|
2915
|
+
# Eligibility rules:
|
|
2916
|
+
# REDIRECT from non-user sources: YES (new constraint)
|
|
2917
|
+
# FEEDBACK from non-user sources with reinforcement >= 2: YES
|
|
2918
|
+
# Everything else: NO
|
|
2919
|
+
eligible_for_merge: (
|
|
2920
|
+
if .type == "REDIRECT" and .source != "user" then true
|
|
2921
|
+
elif .type == "FEEDBACK" and .source != "user" and ((.reinforcement_count // 0) >= 2) then true
|
|
2922
|
+
elif .type == "REDIRECT" and .source == "system" then true
|
|
2923
|
+
else false
|
|
2924
|
+
end
|
|
2925
|
+
),
|
|
2926
|
+
merge_reason: (
|
|
2927
|
+
if .type == "REDIRECT" and .source != "user" then "new \(.source) REDIRECT discovered on branch"
|
|
2928
|
+
elif .type == "FEEDBACK" and .source != "user" and ((.reinforcement_count // 0) >= 2) then "FEEDBACK with reinforcement_count >= 2"
|
|
2929
|
+
elif .type == "FOCUS" then "\(.source)-sourced FOCUS excluded from merge-back"
|
|
2930
|
+
elif .type == "REDIRECT" and .source == "user" then "user signal already on main"
|
|
2931
|
+
elif .type == "FEEDBACK" and .source == "user" then "user signal already on main"
|
|
2932
|
+
elif .type == "FEEDBACK" and ((.reinforcement_count // 0) < 2) then "FEEDBACK reinforcement < 2"
|
|
2933
|
+
else "signal type \(.type) from \(.source) excluded"
|
|
2934
|
+
end
|
|
2935
|
+
)
|
|
2936
|
+
}
|
|
2937
|
+
)
|
|
2938
|
+
' "$peb_file" 2>/dev/null || echo "[]")
|
|
2939
|
+
|
|
2940
|
+
peb_total=$(echo "$peb_all_signals" | jq 'length' 2>/dev/null || echo "0")
|
|
2941
|
+
peb_eligible=$(echo "$peb_all_signals" | jq '[.[] | select(.eligible_for_merge == true)]' 2>/dev/null || echo "[]")
|
|
2942
|
+
peb_ineligible=$(echo "$peb_all_signals" | jq '[.[] | select(.eligible_for_merge == false)]' 2>/dev/null || echo "[]")
|
|
2943
|
+
peb_eligible_count=$(echo "$peb_eligible" | jq 'length' 2>/dev/null || echo "0")
|
|
2944
|
+
peb_ineligible_count=$(echo "$peb_ineligible" | jq 'length' 2>/dev/null || echo "0")
|
|
2945
|
+
|
|
2946
|
+
# Build export signals array with only needed fields
|
|
2947
|
+
peb_export_signals=$(echo "$peb_all_signals" | jq -c '[
|
|
2948
|
+
.[] | {
|
|
2949
|
+
id: .id,
|
|
2950
|
+
type: .type,
|
|
2951
|
+
source: .source,
|
|
2952
|
+
content_hash: .content_hash,
|
|
2953
|
+
content_text: (.content.text // .content // ""),
|
|
2954
|
+
strength: .strength,
|
|
2955
|
+
created_at: .created_at,
|
|
2956
|
+
expires_at: .expires_at,
|
|
2957
|
+
reinforcement_count: (.reinforcement_count // 0),
|
|
2958
|
+
eligible_for_merge: .eligible_for_merge,
|
|
2959
|
+
merge_reason: .merge_reason
|
|
2960
|
+
}
|
|
2961
|
+
]')
|
|
2962
|
+
|
|
2963
|
+
# Write export file
|
|
2964
|
+
peb_export=$(jq -n \
|
|
2965
|
+
--arg schema "pheromone-branch-export-v1" \
|
|
2966
|
+
--arg at "$peb_now_iso" \
|
|
2967
|
+
--arg branch "$peb_branch" \
|
|
2968
|
+
--arg commit "$peb_commit" \
|
|
2969
|
+
--argjson signals "$peb_export_signals" \
|
|
2970
|
+
--argjson total "$peb_total" \
|
|
2971
|
+
--argjson eligible "$peb_eligible_count" \
|
|
2972
|
+
--argjson ineligible "$peb_ineligible_count" \
|
|
2973
|
+
'{
|
|
2974
|
+
schema: $schema,
|
|
2975
|
+
exported_at: $at,
|
|
2976
|
+
branch_name: $branch,
|
|
2977
|
+
branch_commit: $commit,
|
|
2978
|
+
signals: $signals,
|
|
2979
|
+
total_signals: $total,
|
|
2980
|
+
eligible_count: $eligible,
|
|
2981
|
+
ineligible_count: $ineligible
|
|
2982
|
+
}')
|
|
2983
|
+
|
|
2984
|
+
peb_export_dir="$AETHER_ROOT/.aether/exchange"
|
|
2985
|
+
mkdir -p "$peb_export_dir" 2>/dev/null || true
|
|
2986
|
+
peb_export_file="$peb_export_dir/pheromone-branch-export.json"
|
|
2987
|
+
atomic_write "$peb_export_file" "$peb_export" 2>/dev/null || {
|
|
2988
|
+
_aether_log_error "Could not write pheromone branch export"
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
json_ok "$(jq -n \
|
|
2992
|
+
--arg branch "$peb_branch" \
|
|
2993
|
+
--arg commit "$peb_commit" \
|
|
2994
|
+
--argjson total "$peb_total" \
|
|
2995
|
+
--argjson eligible "$peb_eligible_count" \
|
|
2996
|
+
--argjson ineligible "$peb_ineligible_count" \
|
|
2997
|
+
'{
|
|
2998
|
+
branch_name: $branch,
|
|
2999
|
+
branch_commit: $commit,
|
|
3000
|
+
total_signals: $total,
|
|
3001
|
+
eligible_count: $eligible,
|
|
3002
|
+
ineligible_count: $ineligible
|
|
3003
|
+
}')"
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
# ============================================================================
|
|
3007
|
+
# _pheromone_merge_back
|
|
3008
|
+
# Merge branch signals into main (post-merge step)
|
|
3009
|
+
# ============================================================================
|
|
3010
|
+
_pheromone_merge_back() {
|
|
3011
|
+
# Merge eligible branch signals into main's pheromones.json
|
|
3012
|
+
# Usage: pheromone-merge-back [--export-file PATH]
|
|
3013
|
+
# --export-file: path to branch export JSON (default: .aether/exchange/pheromone-branch-export.json)
|
|
3014
|
+
# Returns: JSON with new_signals_written, skipped_count, conflicts_resolved
|
|
3015
|
+
# Side effect: appends to .aether/data/pheromone-merge-log.json
|
|
3016
|
+
|
|
3017
|
+
pmb_export_file="${1:-}"
|
|
3018
|
+
pmb_branch=""
|
|
3019
|
+
|
|
3020
|
+
# Parse args
|
|
3021
|
+
while [[ $# -gt 0 ]]; do
|
|
3022
|
+
case "$1" in
|
|
3023
|
+
--export-file) shift; pmb_export_file="${1:-}" ;;
|
|
3024
|
+
*) shift ;;
|
|
3025
|
+
esac
|
|
3026
|
+
done
|
|
3027
|
+
|
|
3028
|
+
# Default export file path (exchange/ is git-tracked for cross-branch propagation)
|
|
3029
|
+
if [[ -z "$pmb_export_file" ]]; then
|
|
3030
|
+
pmb_export_file="$AETHER_ROOT/.aether/exchange/pheromone-branch-export.json"
|
|
3031
|
+
fi
|
|
3032
|
+
|
|
3033
|
+
# Edge case: no export file -- no-op
|
|
3034
|
+
if [[ ! -f "$pmb_export_file" ]]; then
|
|
3035
|
+
json_ok "$(jq -n '{new_signals_written: 0, skipped_count: 0, conflicts_resolved: [], warnings: []}')"
|
|
3036
|
+
return 0
|
|
3037
|
+
fi
|
|
3038
|
+
|
|
3039
|
+
# Validate export schema
|
|
3040
|
+
pmb_schema=$(jq -r '.schema // ""' "$pmb_export_file" 2>/dev/null || echo "")
|
|
3041
|
+
if [[ "$pmb_schema" != "pheromone-branch-export-v1" ]]; then
|
|
3042
|
+
json_err "$E_VALIDATION_FAILED" "Invalid export file schema: expected pheromone-branch-export-v1"
|
|
3043
|
+
fi
|
|
3044
|
+
|
|
3045
|
+
pmb_branch=$(jq -r '.branch_name // "unknown"' "$pmb_export_file" 2>/dev/null || echo "unknown")
|
|
3046
|
+
pmb_branch_commit=$(jq -r '.branch_commit // "unknown"' "$pmb_export_file" 2>/dev/null || echo "unknown")
|
|
3047
|
+
pmb_now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
3048
|
+
|
|
3049
|
+
pmb_main_file="$COLONY_DATA_DIR/pheromones.json"
|
|
3050
|
+
|
|
3051
|
+
# Initialize main pheromones.json if missing
|
|
3052
|
+
if [[ ! -f "$pmb_main_file" ]]; then
|
|
3053
|
+
pmb_init_content='{"version":"1.0.0","colony_id":"aether-dev","generated_at":"'"$pmb_now_iso"'","signals":[]}'
|
|
3054
|
+
atomic_write "$pmb_main_file" "$pmb_init_content" 2>/dev/null || true
|
|
3055
|
+
fi
|
|
3056
|
+
|
|
3057
|
+
# Get eligible signals from export
|
|
3058
|
+
pmb_eligible=$(jq -c '[.signals[] | select(.eligible_for_merge == true)]' "$pmb_export_file" 2>/dev/null || echo "[]")
|
|
3059
|
+
pmb_ineligible_count=$(jq '[.signals[] | select(.eligible_for_merge == false)] | length' "$pmb_export_file" 2>/dev/null || echo "0")
|
|
3060
|
+
|
|
3061
|
+
# Lock main pheromones.json for writing
|
|
3062
|
+
pmb_lock_held=false
|
|
3063
|
+
if type acquire_lock &>/dev/null; then
|
|
3064
|
+
acquire_lock "$pmb_main_file" 2>/dev/null && pmb_lock_held=true || true
|
|
3065
|
+
fi
|
|
3066
|
+
|
|
3067
|
+
pmb_new_signals="[]"
|
|
3068
|
+
pmb_conflicts="[]"
|
|
3069
|
+
pmb_warnings="[]"
|
|
3070
|
+
pmb_new_count=0
|
|
3071
|
+
|
|
3072
|
+
pmb_eligible_count=$(echo "$pmb_eligible" | jq 'length')
|
|
3073
|
+
|
|
3074
|
+
if [[ "$pmb_eligible_count" -gt 0 ]]; then
|
|
3075
|
+
# Read main signals for conflict detection
|
|
3076
|
+
pmb_main_hashes=$(jq -c '[.signals[] | select(.active == true) | {type: .type, content_hash: .content_hash, id: .id, strength: .strength, source: .source, reinforcement_count: (.reinforcement_count // 0), expires_at: .expires_at}]' "$pmb_main_file" 2>/dev/null || echo "[]")
|
|
3077
|
+
|
|
3078
|
+
# Process each eligible signal
|
|
3079
|
+
pmb_new_signals="[]"
|
|
3080
|
+
pmb_conflicts="[]"
|
|
3081
|
+
|
|
3082
|
+
while IFS= read -r sig; do
|
|
3083
|
+
[[ -z "$sig" || "$sig" == "null" ]] && continue
|
|
3084
|
+
|
|
3085
|
+
sig_type=$(echo "$sig" | jq -r '.type')
|
|
3086
|
+
sig_hash=$(echo "$sig" | jq -r '.content_hash')
|
|
3087
|
+
sig_text=$(echo "$sig" | jq -r '.content_text')
|
|
3088
|
+
sig_strength=$(echo "$sig" | jq -r '.strength')
|
|
3089
|
+
sig_source=$(echo "$sig" | jq -r '.source')
|
|
3090
|
+
sig_reinforcement=$(echo "$sig" | jq -r '.reinforcement_count')
|
|
3091
|
+
sig_expires=$(echo "$sig" | jq -r '.expires_at')
|
|
3092
|
+
sig_id=$(echo "$sig" | jq -r '.id')
|
|
3093
|
+
|
|
3094
|
+
# Check for conflict: main has same type + content_hash
|
|
3095
|
+
main_match=$(echo "$pmb_main_hashes" | jq -c --arg type "$sig_type" --arg hash "$sig_hash" \
|
|
3096
|
+
'[.[] | select(.type == $type and .content_hash == $hash)][0]' 2>/dev/null || echo "null")
|
|
3097
|
+
|
|
3098
|
+
if [[ -n "$main_match" && "$main_match" != "null" ]]; then
|
|
3099
|
+
# Conflict detected -- resolve
|
|
3100
|
+
main_strength=$(echo "$main_match" | jq -r '.strength')
|
|
3101
|
+
main_source=$(echo "$main_match" | jq -r '.source')
|
|
3102
|
+
main_reinforcement=$(echo "$main_match" | jq -r '.reinforcement_count')
|
|
3103
|
+
main_id=$(echo "$main_match" | jq -r '.id')
|
|
3104
|
+
|
|
3105
|
+
# Resolution logic per design spec
|
|
3106
|
+
resolution="skip"
|
|
3107
|
+
|
|
3108
|
+
if [[ "$sig_type" == "REDIRECT" ]]; then
|
|
3109
|
+
resolution="reinforced"
|
|
3110
|
+
elif [[ "$main_source" == "user" && "$sig_type" == "FOCUS" ]]; then
|
|
3111
|
+
resolution="skip"
|
|
3112
|
+
elif [[ "$sig_type" == "FEEDBACK" ]]; then
|
|
3113
|
+
if [[ "$sig_reinforcement" -ge 2 ]]; then
|
|
3114
|
+
resolution="reinforced"
|
|
3115
|
+
else
|
|
3116
|
+
resolution="skip"
|
|
3117
|
+
fi
|
|
3118
|
+
fi
|
|
3119
|
+
|
|
3120
|
+
if [[ "$resolution" == "reinforced" ]]; then
|
|
3121
|
+
# Reinforce: update main signal with max strength, increment reinforcement
|
|
3122
|
+
new_strength=$(echo "$main_strength $sig_strength" | awk '{if ($1 > $2) print $1; else print $2}')
|
|
3123
|
+
new_reinforcement=$(( main_reinforcement + 1 ))
|
|
3124
|
+
|
|
3125
|
+
# Update the signal in main's pheromones.json
|
|
3126
|
+
pmb_updated=$(jq \
|
|
3127
|
+
--arg id "$main_id" \
|
|
3128
|
+
--argjson new_strength "$new_strength" \
|
|
3129
|
+
--argjson new_reinforcement "$new_reinforcement" \
|
|
3130
|
+
--arg now "$pmb_now_iso" \
|
|
3131
|
+
'
|
|
3132
|
+
.signals = [.signals[] |
|
|
3133
|
+
if .id == $id then
|
|
3134
|
+
.strength = ([.strength, $new_strength] | max) |
|
|
3135
|
+
.reinforcement_count = $new_reinforcement |
|
|
3136
|
+
.created_at = $now
|
|
3137
|
+
else .
|
|
3138
|
+
end
|
|
3139
|
+
]
|
|
3140
|
+
' "$pmb_main_file" 2>/dev/null)
|
|
3141
|
+
|
|
3142
|
+
if [[ -n "$pmb_updated" && "$pmb_updated" != "null" ]]; then
|
|
3143
|
+
atomic_write "$pmb_main_file" "$pmb_updated" 2>/dev/null || true
|
|
3144
|
+
fi
|
|
3145
|
+
|
|
3146
|
+
pmb_conflicts=$(echo "$pmb_conflicts" | jq -c --arg hash "$sig_hash" --arg type "$sig_type" \
|
|
3147
|
+
--argjson main_s "$main_strength" --argjson branch_s "$sig_strength" \
|
|
3148
|
+
--argjson new_s "$new_strength" --argjson new_r "$new_reinforcement" \
|
|
3149
|
+
'. += [{
|
|
3150
|
+
content_hash: $hash,
|
|
3151
|
+
type: $type,
|
|
3152
|
+
main_strength: $main_s,
|
|
3153
|
+
branch_strength: $branch_s,
|
|
3154
|
+
resolution: "reinforced",
|
|
3155
|
+
new_strength: $new_s,
|
|
3156
|
+
new_reinforcement_count: $new_r
|
|
3157
|
+
}]')
|
|
3158
|
+
else
|
|
3159
|
+
pmb_conflicts=$(echo "$pmb_conflicts" | jq -c --arg hash "$sig_hash" --arg type "$sig_type" \
|
|
3160
|
+
--argjson main_s "$main_strength" --argjson branch_s "$sig_strength" \
|
|
3161
|
+
'. += [{
|
|
3162
|
+
content_hash: $hash,
|
|
3163
|
+
type: $type,
|
|
3164
|
+
main_strength: $main_s,
|
|
3165
|
+
branch_strength: $branch_s,
|
|
3166
|
+
resolution: "skip"
|
|
3167
|
+
}]')
|
|
3168
|
+
fi
|
|
3169
|
+
else
|
|
3170
|
+
# No conflict -- write new signal to main directly (we already hold the lock,
|
|
3171
|
+
# so calling _pheromone_write would deadlock on re-acquiring it)
|
|
3172
|
+
pmb_new_epoch=$(date +%s)
|
|
3173
|
+
pmb_new_rand=$(( RANDOM % 10000 ))
|
|
3174
|
+
pmb_new_type_lower=$(echo "$sig_type" | tr '[:upper:]' '[:lower:]')
|
|
3175
|
+
pmb_new_id="sig_${pmb_new_type_lower}_${pmb_new_epoch}_${pmb_new_rand}"
|
|
3176
|
+
pmb_new_created="$pmb_now_iso"
|
|
3177
|
+
|
|
3178
|
+
case "$sig_type" in
|
|
3179
|
+
REDIRECT) pmb_new_priority="high" ;;
|
|
3180
|
+
FOCUS) pmb_new_priority="normal" ;;
|
|
3181
|
+
FEEDBACK) pmb_new_priority="low" ;;
|
|
3182
|
+
esac
|
|
3183
|
+
|
|
3184
|
+
pmb_new_signal=$(jq -n \
|
|
3185
|
+
--arg id "$pmb_new_id" \
|
|
3186
|
+
--arg type "$sig_type" \
|
|
3187
|
+
--arg priority "$pmb_new_priority" \
|
|
3188
|
+
--arg source "$sig_source" \
|
|
3189
|
+
--arg created_at "$pmb_new_created" \
|
|
3190
|
+
--arg expires_at "phase_end" \
|
|
3191
|
+
--argjson active true \
|
|
3192
|
+
--argjson strength "$sig_strength" \
|
|
3193
|
+
--arg reason "Merged from branch $pmb_branch" \
|
|
3194
|
+
--arg content "$sig_text" \
|
|
3195
|
+
--arg content_hash "$sig_hash" \
|
|
3196
|
+
--argjson reinforcement_count 0 \
|
|
3197
|
+
'{id: $id, type: $type, priority: $priority, source: $source, created_at: $created_at, expires_at: $expires_at, active: $active, strength: ($strength | tonumber), reason: $reason, content: {text: $content}, content_hash: $content_hash, reinforcement_count: $reinforcement_count}')
|
|
3198
|
+
|
|
3199
|
+
pmb_updated_main=$(jq --argjson sig "$pmb_new_signal" '.signals += [$sig]' "$pmb_main_file" 2>/dev/null)
|
|
3200
|
+
if [[ -n "$pmb_updated_main" && "$pmb_updated_main" != "null" ]]; then
|
|
3201
|
+
atomic_write "$pmb_main_file" "$pmb_updated_main" 2>/dev/null || true
|
|
3202
|
+
fi
|
|
3203
|
+
|
|
3204
|
+
pmb_new_signals=$(echo "$pmb_new_signals" | jq -c --arg id "$sig_id" --arg new_id "$pmb_new_id" --arg type "$sig_type" --arg hash "$sig_hash" \
|
|
3205
|
+
'. += [{original_id: $id, new_id: $new_id, type: $type, content_hash: $hash}]')
|
|
3206
|
+
pmb_new_count=$(( pmb_new_count + 1 ))
|
|
3207
|
+
fi
|
|
3208
|
+
done < <(echo "$pmb_eligible" | jq -c '.[]')
|
|
3209
|
+
fi
|
|
3210
|
+
|
|
3211
|
+
# Release lock
|
|
3212
|
+
[[ "$pmb_lock_held" == "true" ]] && release_lock 2>/dev/null || true
|
|
3213
|
+
|
|
3214
|
+
# Append to merge log
|
|
3215
|
+
pmb_log_file="$COLONY_DATA_DIR/pheromone-merge-log.json"
|
|
3216
|
+
pmb_entries="[]"
|
|
3217
|
+
if [[ -f "$pmb_log_file" ]]; then
|
|
3218
|
+
pmb_entries=$(jq -c '.entries // []' "$pmb_log_file" 2>/dev/null || echo "[]")
|
|
3219
|
+
fi
|
|
3220
|
+
|
|
3221
|
+
pmb_new_entry=$(jq -n \
|
|
3222
|
+
--arg branch "$pmb_branch" \
|
|
3223
|
+
--arg commit "$pmb_branch_commit" \
|
|
3224
|
+
--arg at "$pmb_now_iso" \
|
|
3225
|
+
--argjson new_signals "$pmb_new_signals" \
|
|
3226
|
+
--argjson conflicts "$pmb_conflicts" \
|
|
3227
|
+
--argjson warnings "$pmb_warnings" \
|
|
3228
|
+
--argjson skipped "$pmb_ineligible_count" \
|
|
3229
|
+
'{
|
|
3230
|
+
merged_from_branch: $branch,
|
|
3231
|
+
merged_from_commit: $commit,
|
|
3232
|
+
merged_at: $at,
|
|
3233
|
+
new_signals_written: $new_signals,
|
|
3234
|
+
conflicts_resolved: $conflicts,
|
|
3235
|
+
warnings: $warnings,
|
|
3236
|
+
skipped_count: $skipped
|
|
3237
|
+
}')
|
|
3238
|
+
|
|
3239
|
+
pmb_updated_log=$(jq -n --arg schema "pheromone-merge-log-v1" --argjson entries "$pmb_entries" --argjson new_entry "$pmb_new_entry" \
|
|
3240
|
+
'{schema: $schema, entries: ($entries + [$new_entry])}')
|
|
3241
|
+
|
|
3242
|
+
atomic_write "$pmb_log_file" "$pmb_updated_log" 2>/dev/null || {
|
|
3243
|
+
_aether_log_error "Could not write pheromone merge log"
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
json_ok "$(jq -n \
|
|
3247
|
+
--arg branch "$pmb_branch" \
|
|
3248
|
+
--argjson new_count "$pmb_new_count" \
|
|
3249
|
+
--argjson skipped "$pmb_ineligible_count" \
|
|
3250
|
+
--argjson conflicts "$pmb_conflicts" \
|
|
3251
|
+
'{
|
|
3252
|
+
merged_from_branch: $branch,
|
|
3253
|
+
new_signals_written: $new_count,
|
|
3254
|
+
skipped_count: $skipped,
|
|
3255
|
+
conflicts_resolved: $conflicts
|
|
3256
|
+
}')"
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
# ============================================================================
|
|
3260
|
+
# _pheromone_merge_log
|
|
3261
|
+
# Read merge log entries for debugging/auditing
|
|
3262
|
+
# ============================================================================
|
|
3263
|
+
_pheromone_merge_log() {
|
|
3264
|
+
# Read pheromone merge log entries
|
|
3265
|
+
# Usage: pheromone-merge-log [--last N]
|
|
3266
|
+
# --last N: only return the last N entries (default: all)
|
|
3267
|
+
# Returns: JSON with entries array
|
|
3268
|
+
|
|
3269
|
+
pml_last=""
|
|
3270
|
+
while [[ $# -gt 0 ]]; do
|
|
3271
|
+
case "$1" in
|
|
3272
|
+
--last) shift; pml_last="${1:-}" ;;
|
|
3273
|
+
*) shift ;;
|
|
3274
|
+
esac
|
|
3275
|
+
done
|
|
3276
|
+
|
|
3277
|
+
pml_log_file="$COLONY_DATA_DIR/pheromone-merge-log.json"
|
|
3278
|
+
|
|
3279
|
+
if [[ ! -f "$pml_log_file" ]]; then
|
|
3280
|
+
json_ok "$(jq -n '{schema: "pheromone-merge-log-v1", entries_count: 0, entries: []}')"
|
|
3281
|
+
return 0
|
|
3282
|
+
fi
|
|
3283
|
+
|
|
3284
|
+
pml_entries=$(jq -c '.entries // []' "$pml_log_file" 2>/dev/null || echo "[]")
|
|
3285
|
+
|
|
3286
|
+
if [[ -n "$pml_last" && "$pml_last" =~ ^[0-9]+$ ]]; then
|
|
3287
|
+
pml_entries=$(echo "$pml_entries" | jq -c --argjson n "$pml_last" '.[(-$n):]')
|
|
3288
|
+
fi
|
|
3289
|
+
|
|
3290
|
+
pml_count=$(echo "$pml_entries" | jq 'length' 2>/dev/null || echo "0")
|
|
3291
|
+
|
|
3292
|
+
json_ok "$(jq -n \
|
|
3293
|
+
--argjson count "$pml_count" \
|
|
3294
|
+
--argjson entries "$pml_entries" \
|
|
3295
|
+
'{entries_count: $count, entries: $entries}')"
|
|
3296
|
+
}
|
|
3297
|
+
|