aether-colony 5.2.1 → 5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aether/aether-utils.sh +35 -0
- 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/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/docs/command-playbooks/build-full.md +1 -1
- package/.aether/docs/command-playbooks/build-prep.md +10 -3
- 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-verify.md +32 -0
- package/.aether/utils/clash-detect.sh +239 -0
- package/.aether/utils/hooks/clash-pre-tool-use.js +99 -0
- package/.aether/utils/merge-driver-lockfile.sh +35 -0
- package/.aether/utils/midden.sh +534 -0
- package/.aether/utils/pheromone.sh +1376 -108
- package/.aether/utils/queen.sh +2 -4
- package/.aether/utils/state-api.sh +25 -4
- package/.aether/utils/swarm.sh +1 -1
- package/.aether/utils/worktree.sh +189 -0
- package/CHANGELOG.md +26 -0
- package/README.md +161 -161
- 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 +2 -2
- 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
package/.aether/utils/midden.sh
CHANGED
|
@@ -518,3 +518,537 @@ _midden_acknowledge() {
|
|
|
518
518
|
'{acknowledged: true, count: $count, reason: $reason}')"
|
|
519
519
|
return 0
|
|
520
520
|
}
|
|
521
|
+
|
|
522
|
+
# ============================================================================
|
|
523
|
+
# Cross-Branch Midden Collection (Phase 41)
|
|
524
|
+
# ============================================================================
|
|
525
|
+
|
|
526
|
+
_midden_collect() {
|
|
527
|
+
# Collect midden entries from a merged branch worktree into main's midden
|
|
528
|
+
# Usage: midden-collect --branch <name> --merge-sha <sha> [--dry-run]
|
|
529
|
+
# Returns: JSON with collection status and counts
|
|
530
|
+
#
|
|
531
|
+
# Dual-layer idempotency:
|
|
532
|
+
# Layer 1: Merge fingerprint in collected-merges.json (fast path)
|
|
533
|
+
# Layer 2: Per-entry ID dedup (safety net)
|
|
534
|
+
|
|
535
|
+
mc_branch=""
|
|
536
|
+
mc_merge_sha=""
|
|
537
|
+
mc_dry_run=false
|
|
538
|
+
|
|
539
|
+
while [[ $# -gt 0 ]]; do
|
|
540
|
+
case "$1" in
|
|
541
|
+
--branch) mc_branch="${2:-}"; shift 2 ;;
|
|
542
|
+
--merge-sha) mc_merge_sha="${2:-}"; shift 2 ;;
|
|
543
|
+
--dry-run) mc_dry_run=true; shift ;;
|
|
544
|
+
*) shift ;;
|
|
545
|
+
esac
|
|
546
|
+
done
|
|
547
|
+
|
|
548
|
+
# Validate required args
|
|
549
|
+
if [[ -z "$mc_branch" ]]; then
|
|
550
|
+
json_err "$E_VALIDATION_FAILED" "midden-collect requires --branch"
|
|
551
|
+
fi
|
|
552
|
+
if [[ -z "$mc_merge_sha" ]]; then
|
|
553
|
+
json_err "$E_VALIDATION_FAILED" "midden-collect requires --merge-sha"
|
|
554
|
+
fi
|
|
555
|
+
|
|
556
|
+
# Resolve worktree midden path
|
|
557
|
+
mc_worktree_midden=""
|
|
558
|
+
mc_candidate="$AETHER_ROOT/.aether/worktrees/$mc_branch/.aether/data/midden/midden.json"
|
|
559
|
+
|
|
560
|
+
if [[ -f "$mc_candidate" ]]; then
|
|
561
|
+
mc_worktree_midden="$mc_candidate"
|
|
562
|
+
else
|
|
563
|
+
# Fallback: check git worktree list
|
|
564
|
+
mc_wt_path=$(git -C "$AETHER_ROOT" worktree list --porcelain 2>/dev/null | grep -F "worktree" | head -1 | cut -d' ' -f2 || true)
|
|
565
|
+
if [[ -n "$mc_wt_path" && -f "$mc_wt_path/.aether/data/midden/midden.json" ]]; then
|
|
566
|
+
mc_worktree_midden="$mc_wt_path/.aether/data/midden/midden.json"
|
|
567
|
+
fi
|
|
568
|
+
fi
|
|
569
|
+
|
|
570
|
+
if [[ -z "$mc_worktree_midden" || ! -f "$mc_worktree_midden" ]]; then
|
|
571
|
+
json_ok "$(jq -n --arg branch "$mc_branch" \
|
|
572
|
+
'{status: "worktree_not_found", entries_collected: 0, branch: $branch}')"
|
|
573
|
+
return 0
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
# Read branch midden.json
|
|
577
|
+
mc_branch_data=$(cat "$mc_worktree_midden" 2>/dev/null || echo "")
|
|
578
|
+
|
|
579
|
+
if [[ -z "$mc_branch_data" ]]; then
|
|
580
|
+
json_ok '{"status":"empty_branch_midden","entries_collected":0}'
|
|
581
|
+
return 0
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
# Validate branch midden.json is valid JSON
|
|
585
|
+
if ! echo "$mc_branch_data" | jq empty 2>/dev/null; then
|
|
586
|
+
json_err "$E_INTERNAL" "Branch midden.json is corrupt"
|
|
587
|
+
fi
|
|
588
|
+
|
|
589
|
+
# Check for entries
|
|
590
|
+
mc_branch_count=$(echo "$mc_branch_data" | jq '[.entries[]?] | length' 2>/dev/null || echo "0")
|
|
591
|
+
if [[ "$mc_branch_count" -eq 0 ]]; then
|
|
592
|
+
json_ok '{"status":"empty_branch_midden","entries_collected":0}'
|
|
593
|
+
return 0
|
|
594
|
+
fi
|
|
595
|
+
|
|
596
|
+
mc_midden_dir="$COLONY_DATA_DIR/midden"
|
|
597
|
+
mc_midden_file="$mc_midden_dir/midden.json"
|
|
598
|
+
mc_merges_file="$mc_midden_dir/collected-merges.json"
|
|
599
|
+
mc_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
600
|
+
|
|
601
|
+
mkdir -p "$mc_midden_dir"
|
|
602
|
+
|
|
603
|
+
# Initialize midden.json if missing
|
|
604
|
+
if [[ ! -f "$mc_midden_file" ]]; then
|
|
605
|
+
printf '%s\n' '{"version":"1.0.0","entries":[]}' > "$mc_midden_file"
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
# Initialize collected-merges.json if missing
|
|
609
|
+
if [[ ! -f "$mc_merges_file" ]]; then
|
|
610
|
+
printf '%s\n' '{"version":"1.0.0","merges":[]}' > "$mc_merges_file"
|
|
611
|
+
fi
|
|
612
|
+
|
|
613
|
+
# LAYER 1: Check merge fingerprint
|
|
614
|
+
mc_already=$(jq --arg sha "$mc_merge_sha" --arg branch "$mc_branch" \
|
|
615
|
+
'[.merges[]? | select(.merge_commit == $sha and .branch_name == $branch)] | length > 0' \
|
|
616
|
+
"$mc_merges_file" 2>/dev/null || echo "false")
|
|
617
|
+
|
|
618
|
+
if [[ "$mc_already" == "true" ]]; then
|
|
619
|
+
json_ok "$(jq -n --arg sha "$mc_merge_sha" --arg branch "$mc_branch" \
|
|
620
|
+
'{status: "already_collected", merge_commit: $sha, branch: $branch, entries_collected: 0}')"
|
|
621
|
+
return 0
|
|
622
|
+
fi
|
|
623
|
+
|
|
624
|
+
if [[ "$mc_dry_run" == "true" ]]; then
|
|
625
|
+
json_ok "$(jq -n --arg branch "$mc_branch" --arg sha "$mc_merge_sha" --argjson count "$mc_branch_count" \
|
|
626
|
+
'{status: "dry_run", branch: $branch, merge_commit: $sha, entries_would_collect: $count}')"
|
|
627
|
+
return 0
|
|
628
|
+
fi
|
|
629
|
+
|
|
630
|
+
# LAYER 2: Per-entry ID dedup — get existing IDs from main's midden
|
|
631
|
+
mc_existing_ids=$(jq -r '[.entries[]?.id] | map(select(. != null))' "$mc_midden_file" 2>/dev/null || echo "[]")
|
|
632
|
+
|
|
633
|
+
# Filter branch entries: exclude those with IDs already in main, then enrich
|
|
634
|
+
mc_new_entries=$(jq --argjson existing_ids "$mc_existing_ids" \
|
|
635
|
+
--arg branch "$mc_branch" \
|
|
636
|
+
--arg ts "$mc_timestamp" \
|
|
637
|
+
--arg sha "$mc_merge_sha" \
|
|
638
|
+
'
|
|
639
|
+
[.entries[]?] |
|
|
640
|
+
map(select([.id] | inside($existing_ids) | not)) |
|
|
641
|
+
map(. + {
|
|
642
|
+
collected_from: $branch,
|
|
643
|
+
collected_at: $ts,
|
|
644
|
+
merge_commit: $sha,
|
|
645
|
+
original_entry_id: .id
|
|
646
|
+
})
|
|
647
|
+
' "$mc_worktree_midden" 2>/dev/null || echo "[]")
|
|
648
|
+
|
|
649
|
+
mc_new_count=$(echo "$mc_new_entries" | jq 'length' 2>/dev/null || echo "0")
|
|
650
|
+
mc_skipped=$((mc_branch_count - mc_new_count))
|
|
651
|
+
|
|
652
|
+
if [[ "$mc_new_count" -gt 0 ]]; then
|
|
653
|
+
# Append enriched entries to main's midden
|
|
654
|
+
acquire_lock "$mc_midden_file" || {
|
|
655
|
+
json_err "$E_LOCK_FAILED" "Failed to acquire lock on midden.json"
|
|
656
|
+
}
|
|
657
|
+
trap 'release_lock 2>/dev/null || true' EXIT
|
|
658
|
+
|
|
659
|
+
mc_updated=$(jq --argjson new_entries "$mc_new_entries" \
|
|
660
|
+
'.entries += $new_entries' "$mc_midden_file" 2>/dev/null)
|
|
661
|
+
|
|
662
|
+
if [[ -z "$mc_updated" ]]; then
|
|
663
|
+
trap - EXIT
|
|
664
|
+
release_lock 2>/dev/null || true
|
|
665
|
+
json_err "$E_INTERNAL" "Failed to update midden.json with collected entries"
|
|
666
|
+
fi
|
|
667
|
+
|
|
668
|
+
atomic_write "$mc_midden_file" "$mc_updated"
|
|
669
|
+
|
|
670
|
+
trap - EXIT
|
|
671
|
+
release_lock 2>/dev/null || true
|
|
672
|
+
fi
|
|
673
|
+
|
|
674
|
+
# Write fingerprint to collected-merges.json
|
|
675
|
+
mc_fingerprint=$(printf '%s|%s|%d' "$mc_branch" "$mc_merge_sha" "$mc_new_count" | shasum -a 256 | cut -d' ' -f1)
|
|
676
|
+
|
|
677
|
+
acquire_lock "$mc_merges_file" || {
|
|
678
|
+
# Non-fatal — entries were collected but fingerprint may be missing
|
|
679
|
+
json_ok "$(jq -n --arg branch "$mc_branch" --arg sha "$mc_merge_sha" \
|
|
680
|
+
--argjson collected "$mc_new_count" --argjson skipped "$mc_skipped" \
|
|
681
|
+
'{status: "collected", entries_collected: $collected, entries_skipped_dup: $skipped, branch: $branch, merge_commit: $sha, warning: "fingerprint_write_failed"}')"
|
|
682
|
+
return 0
|
|
683
|
+
}
|
|
684
|
+
trap 'release_lock 2>/dev/null || true' EXIT
|
|
685
|
+
|
|
686
|
+
mc_merges_updated=$(jq --arg sha "$mc_merge_sha" --arg branch "$mc_branch" \
|
|
687
|
+
--arg ts "$mc_timestamp" --argjson collected "$mc_new_count" \
|
|
688
|
+
--argjson skipped "$mc_skipped" --arg fp "$mc_fingerprint" \
|
|
689
|
+
'.merges += [{
|
|
690
|
+
merge_commit: $sha,
|
|
691
|
+
branch_name: $branch,
|
|
692
|
+
collected_at: $ts,
|
|
693
|
+
entries_collected: $collected,
|
|
694
|
+
entries_skipped_dup: $skipped,
|
|
695
|
+
fingerprint: $fp
|
|
696
|
+
}]' "$mc_merges_file" 2>/dev/null)
|
|
697
|
+
|
|
698
|
+
if [[ -n "$mc_merges_updated" ]]; then
|
|
699
|
+
atomic_write "$mc_merges_file" "$mc_merges_updated"
|
|
700
|
+
fi
|
|
701
|
+
|
|
702
|
+
trap - EXIT
|
|
703
|
+
release_lock 2>/dev/null || true
|
|
704
|
+
|
|
705
|
+
json_ok "$(jq -n --arg branch "$mc_branch" --arg sha "$mc_merge_sha" \
|
|
706
|
+
--argjson collected "$mc_new_count" --argjson skipped "$mc_skipped" \
|
|
707
|
+
'{status: "collected", entries_collected: $collected, entries_skipped_dup: $skipped, branch: $branch, merge_commit: $sha}')"
|
|
708
|
+
return 0
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
_midden_handle_revert() {
|
|
712
|
+
# Tag entries from a reverted merge commit (not delete)
|
|
713
|
+
# Usage: midden-handle-revert --sha <revert-sha>
|
|
714
|
+
# OR: midden-handle-revert --revert-commit <sha> --original-merge <sha>
|
|
715
|
+
# Returns: JSON with revert status and tagged count
|
|
716
|
+
|
|
717
|
+
mhr_revert_sha=""
|
|
718
|
+
mhr_original_merge=""
|
|
719
|
+
|
|
720
|
+
while [[ $# -gt 0 ]]; do
|
|
721
|
+
case "$1" in
|
|
722
|
+
--sha) mhr_revert_sha="${2:-}"; shift 2 ;;
|
|
723
|
+
--revert-commit) mhr_revert_sha="${2:-}"; shift 2 ;;
|
|
724
|
+
--original-merge) mhr_original_merge="${2:-}"; shift 2 ;;
|
|
725
|
+
*) shift ;;
|
|
726
|
+
esac
|
|
727
|
+
done
|
|
728
|
+
|
|
729
|
+
if [[ -z "$mhr_revert_sha" ]]; then
|
|
730
|
+
json_err "$E_VALIDATION_FAILED" "midden-handle-revert requires --sha or --revert-commit"
|
|
731
|
+
fi
|
|
732
|
+
|
|
733
|
+
mc_midden_dir="$COLONY_DATA_DIR/midden"
|
|
734
|
+
mc_merges_file="$mc_midden_dir/collected-merges.json"
|
|
735
|
+
|
|
736
|
+
# If no original-merge given, try to find it from collected-merges by parsing commit message
|
|
737
|
+
if [[ -z "$mhr_original_merge" ]]; then
|
|
738
|
+
# Try git log to find the reverted merge
|
|
739
|
+
mhr_original_merge=$(git -C "$AETHER_ROOT" log -1 --format="%b" "$mhr_revert_sha" 2>/dev/null \
|
|
740
|
+
| grep -oE '[0-9a-f]{7,40}' | head -1 || true)
|
|
741
|
+
fi
|
|
742
|
+
|
|
743
|
+
if [[ -z "$mhr_original_merge" ]]; then
|
|
744
|
+
json_ok "$(jq -n --arg sha "$mhr_revert_sha" \
|
|
745
|
+
'{status: "original_merge_not_resolved", revert_commit: $sha, entries_tagged: 0}')"
|
|
746
|
+
return 0
|
|
747
|
+
fi
|
|
748
|
+
|
|
749
|
+
# Check collected-merges.json exists
|
|
750
|
+
if [[ ! -f "$mc_merges_file" ]]; then
|
|
751
|
+
json_ok "$(jq -n --arg sha "$mhr_revert_sha" --arg merge "$mhr_original_merge" \
|
|
752
|
+
'{status: "merge_not_found", revert_commit: $sha, original_merge: $merge, entries_tagged: 0}')"
|
|
753
|
+
return 0
|
|
754
|
+
fi
|
|
755
|
+
|
|
756
|
+
# Check if the original merge exists in collected-merges
|
|
757
|
+
mhr_found=$(jq --arg merge "$mhr_original_merge" \
|
|
758
|
+
'[.merges[]? | select(.merge_commit == $merge)] | length > 0' \
|
|
759
|
+
"$mc_merges_file" 2>/dev/null || echo "false")
|
|
760
|
+
|
|
761
|
+
if [[ "$mhr_found" != "true" ]]; then
|
|
762
|
+
json_ok "$(jq -n --arg sha "$mhr_revert_sha" --arg merge "$mhr_original_merge" \
|
|
763
|
+
'{status: "merge_not_found", revert_commit: $sha, original_merge: $merge, entries_tagged: 0}')"
|
|
764
|
+
return 0
|
|
765
|
+
fi
|
|
766
|
+
|
|
767
|
+
mhr_midden_file="$mc_midden_dir/midden.json"
|
|
768
|
+
mhr_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
769
|
+
mhr_tagged=0
|
|
770
|
+
|
|
771
|
+
# Tag entries in main's midden.json
|
|
772
|
+
if [[ -f "$mhr_midden_file" ]]; then
|
|
773
|
+
acquire_lock "$mhr_midden_file" || {
|
|
774
|
+
json_err "$E_LOCK_FAILED" "Failed to acquire lock on midden.json"
|
|
775
|
+
}
|
|
776
|
+
trap 'release_lock 2>/dev/null || true' EXIT
|
|
777
|
+
|
|
778
|
+
mhr_updated=$(jq --arg revert_sha "$mhr_revert_sha" --arg merge_sha "$mhr_original_merge" \
|
|
779
|
+
'
|
|
780
|
+
.entries = [.entries[] |
|
|
781
|
+
if .merge_commit == $merge_sha then
|
|
782
|
+
. + {
|
|
783
|
+
tags: ((.tags // []) + ["reverted:" + $revert_sha]) | unique,
|
|
784
|
+
reviewed: false
|
|
785
|
+
}
|
|
786
|
+
else
|
|
787
|
+
.
|
|
788
|
+
end
|
|
789
|
+
]
|
|
790
|
+
' "$mhr_midden_file" 2>/dev/null)
|
|
791
|
+
|
|
792
|
+
if [[ -n "$mhr_updated" ]]; then
|
|
793
|
+
atomic_write "$mhr_midden_file" "$mhr_updated"
|
|
794
|
+
mhr_tagged=$(echo "$mhr_updated" | jq --arg merge_sha "$mhr_original_merge" \
|
|
795
|
+
'[.entries[] | select(.merge_commit == $merge_sha)] | length' 2>/dev/null || echo "0")
|
|
796
|
+
fi
|
|
797
|
+
|
|
798
|
+
trap - EXIT
|
|
799
|
+
release_lock 2>/dev/null || true
|
|
800
|
+
fi
|
|
801
|
+
|
|
802
|
+
# Update collected-merges.json: mark merge as reverted
|
|
803
|
+
acquire_lock "$mc_merges_file" || true
|
|
804
|
+
trap 'release_lock 2>/dev/null || true' EXIT
|
|
805
|
+
|
|
806
|
+
mhr_merges_updated=$(jq --arg revert_sha "$mhr_revert_sha" \
|
|
807
|
+
--arg merge_sha "$mhr_original_merge" --arg ts "$mhr_timestamp" \
|
|
808
|
+
'
|
|
809
|
+
.merges = [.merges[] |
|
|
810
|
+
if .merge_commit == $merge_sha then
|
|
811
|
+
. + {reverted_by: $revert_sha, reverted_at: $ts, status: "reverted"}
|
|
812
|
+
else
|
|
813
|
+
.
|
|
814
|
+
end
|
|
815
|
+
]
|
|
816
|
+
' "$mc_merges_file" 2>/dev/null)
|
|
817
|
+
|
|
818
|
+
if [[ -n "$mhr_merges_updated" ]]; then
|
|
819
|
+
atomic_write "$mc_merges_file" "$mhr_merges_updated"
|
|
820
|
+
fi
|
|
821
|
+
|
|
822
|
+
trap - EXIT
|
|
823
|
+
release_lock 2>/dev/null || true
|
|
824
|
+
|
|
825
|
+
json_ok "$(jq -n --arg sha "$mhr_revert_sha" --arg merge "$mhr_original_merge" \
|
|
826
|
+
--argjson tagged "$mhr_tagged" \
|
|
827
|
+
'{revert_commit: $sha, original_merge: $merge, entries_tagged: $tagged, entries_deleted: 0}')"
|
|
828
|
+
return 0
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
_midden_cross_pr_analysis() {
|
|
832
|
+
# Detect cross-PR failure patterns and auto-emit REDIRECT for systemic issues
|
|
833
|
+
# Usage: midden-cross-pr-analysis [--category <cat>] [--window <days>]
|
|
834
|
+
# Returns: JSON with category analysis, scores, classifications
|
|
835
|
+
|
|
836
|
+
mca_category=""
|
|
837
|
+
mca_window=14
|
|
838
|
+
|
|
839
|
+
while [[ $# -gt 0 ]]; do
|
|
840
|
+
case "$1" in
|
|
841
|
+
--category) mca_category="${2:-}"; shift 2 ;;
|
|
842
|
+
--window) mca_window="${2:-14}"; shift 2 ;;
|
|
843
|
+
*) shift ;;
|
|
844
|
+
esac
|
|
845
|
+
done
|
|
846
|
+
|
|
847
|
+
mca_midden_file="$COLONY_DATA_DIR/midden/midden.json"
|
|
848
|
+
|
|
849
|
+
if [[ ! -f "$mca_midden_file" ]]; then
|
|
850
|
+
json_ok "$(jq -n --argjson window "$mca_window" \
|
|
851
|
+
'{analysis_timestamp: "now", window_days: $window, total_entries_scanned: 0, categories: {}, systemic_categories: []}')"
|
|
852
|
+
return 0
|
|
853
|
+
fi
|
|
854
|
+
|
|
855
|
+
mca_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
856
|
+
|
|
857
|
+
# Calculate cutoff timestamp: NOW - window_days
|
|
858
|
+
mca_cutoff=$(date -u -v-"${mca_window}d" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
|
859
|
+
python3 -c "import datetime; print((datetime.datetime.utcnow() - datetime.timedelta(days=$mca_window)).strftime('%Y-%m-%dT%H:%M:%SZ'))" 2>/dev/null || \
|
|
860
|
+
date -u -d "$mca_window days ago" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
861
|
+
|
|
862
|
+
mca_result=$(jq --arg cutoff "$mca_cutoff" \
|
|
863
|
+
--arg category "$mca_category" --argjson window "$mca_window" \
|
|
864
|
+
'
|
|
865
|
+
# Collect cross-branch entries within window, excluding reverted
|
|
866
|
+
[.entries // [] | .[] |
|
|
867
|
+
select(.collected_from != null) |
|
|
868
|
+
select(.reviewed != true) |
|
|
869
|
+
select(.tags // [] | map(startswith("reverted:")) | any | not) |
|
|
870
|
+
select(if ($cutoff | length) > 0 then .timestamp >= $cutoff else true end) |
|
|
871
|
+
if ($category | length) > 0 then select(.category == $category) else . end
|
|
872
|
+
] |
|
|
873
|
+
. as $entries |
|
|
874
|
+
|
|
875
|
+
# Group by category
|
|
876
|
+
($entries | group_by(.category) | map({key: .[0].category, value: .}) | from_entries) as $by_cat |
|
|
877
|
+
|
|
878
|
+
# Compute metrics per category
|
|
879
|
+
($by_cat | to_entries | map({
|
|
880
|
+
key: .key,
|
|
881
|
+
value: {
|
|
882
|
+
total_entries: (.value | length),
|
|
883
|
+
unique_prs: ([.value[].collected_from] | unique | length),
|
|
884
|
+
entries_per_pr: (.value | group_by(.collected_from) | map({key: .[0].collected_from, value: length}) | from_entries),
|
|
885
|
+
cross_pr_score: (
|
|
886
|
+
(([.value[].collected_from] | unique | length) / 5) * 0.6 +
|
|
887
|
+
((.value | length) / 10) * 0.4
|
|
888
|
+
),
|
|
889
|
+
classification: (
|
|
890
|
+
if (([.value[].collected_from] | unique | length) >= 3) and ((.value | length) >= 5) then
|
|
891
|
+
"cross-pr-critical"
|
|
892
|
+
elif (([.value[].collected_from] | unique | length) >= 2) and ((.value | length) >= 3) then
|
|
893
|
+
"cross-pr-systemic"
|
|
894
|
+
else
|
|
895
|
+
"single-pr"
|
|
896
|
+
end
|
|
897
|
+
),
|
|
898
|
+
auto_redirect_emitted: false
|
|
899
|
+
}
|
|
900
|
+
}) | from_entries) as $analysis |
|
|
901
|
+
|
|
902
|
+
# Collect systemic categories
|
|
903
|
+
[$analysis | to_entries[] | select(.value.classification == "cross-pr-systemic" or .value.classification == "cross-pr-critical") | .key] as $systemic |
|
|
904
|
+
|
|
905
|
+
{
|
|
906
|
+
total_entries_scanned: ($entries | length),
|
|
907
|
+
categories: $analysis,
|
|
908
|
+
systemic_categories: $systemic
|
|
909
|
+
}
|
|
910
|
+
' "$mca_midden_file" 2>/dev/null)
|
|
911
|
+
|
|
912
|
+
if [[ -z "$mca_result" ]]; then
|
|
913
|
+
json_ok "$(jq -n --argjson window "$mca_window" \
|
|
914
|
+
'{analysis_timestamp: "now", window_days: $window, total_entries_scanned: 0, categories: {}, systemic_categories: []}')"
|
|
915
|
+
return 0
|
|
916
|
+
fi
|
|
917
|
+
|
|
918
|
+
# Auto-emit REDIRECT for systemic/critical categories
|
|
919
|
+
mca_systemic=$(echo "$mca_result" | jq -r '.systemic_categories // [] | .[]' 2>/dev/null || true)
|
|
920
|
+
for mca_cat in $mca_systemic; do
|
|
921
|
+
mca_cat_data=$(echo "$mca_result" | jq --arg cat "$mca_cat" '.categories[$cat]' 2>/dev/null || echo "{}")
|
|
922
|
+
mca_unique_prs=$(echo "$mca_cat_data" | jq -r '.unique_prs // 0' 2>/dev/null || echo "0")
|
|
923
|
+
mca_total=$(echo "$mca_cat_data" | jq -r '.total_entries // 0' 2>/dev/null || echo "0")
|
|
924
|
+
mca_score=$(echo "$mca_cat_data" | jq -r '.cross_pr_score // 0' 2>/dev/null || echo "0")
|
|
925
|
+
|
|
926
|
+
# Compute strength: 0.5 + (score * 0.5), capped at 1.0
|
|
927
|
+
mca_strength=$(jq -n --argjson score "$mca_score" '0.5 + ($score * 0.5) | if . > 1.0 then 1.0 else . * 100 | round / 100 end' 2>/dev/null || echo "0.7")
|
|
928
|
+
|
|
929
|
+
# NON-BLOCKING: emit REDIRECT, swallow all output
|
|
930
|
+
bash "$AETHER_ROOT/.aether/aether-utils.sh" pheromone-write REDIRECT \
|
|
931
|
+
"[cross-pr-pattern] $mca_cat failures across $mca_unique_prs PRs in $mca_window days ($mca_total entries)" \
|
|
932
|
+
--strength "$mca_strength" \
|
|
933
|
+
--source "auto:cross-pr" \
|
|
934
|
+
--reason "Auto-emitted: cross-PR systemic failure pattern detected" \
|
|
935
|
+
--ttl "30d" >/dev/null 2>&1 || true
|
|
936
|
+
|
|
937
|
+
# Mark as emitted in the result
|
|
938
|
+
mca_result=$(echo "$mca_result" | jq --arg cat "$mca_cat" '.categories[$cat].auto_redirect_emitted = true' 2>/dev/null || echo "$mca_result")
|
|
939
|
+
done
|
|
940
|
+
|
|
941
|
+
json_ok "$(echo "$mca_result" | jq --arg ts "$mca_timestamp" --argjson window "$mca_window" \
|
|
942
|
+
'. + {analysis_timestamp: $ts, window_days: $window}' 2>/dev/null || echo "$mca_result")"
|
|
943
|
+
return 0
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
_midden_prune() {
|
|
947
|
+
# Retention cleanup for collected merges and reverted entries
|
|
948
|
+
# Usage: midden-prune --stale-merges
|
|
949
|
+
# OR: midden-prune --reverted --age <days>
|
|
950
|
+
# Returns: JSON with prune counts
|
|
951
|
+
|
|
952
|
+
mp_stale_merges=false
|
|
953
|
+
mp_reverted=false
|
|
954
|
+
mp_age=30
|
|
955
|
+
|
|
956
|
+
while [[ $# -gt 0 ]]; do
|
|
957
|
+
case "$1" in
|
|
958
|
+
--stale-merges) mp_stale_merges=true; shift ;;
|
|
959
|
+
--reverted) mp_reverted=true; shift ;;
|
|
960
|
+
--age) mp_age="${2:-30}"; shift 2 ;;
|
|
961
|
+
*) shift ;;
|
|
962
|
+
esac
|
|
963
|
+
done
|
|
964
|
+
|
|
965
|
+
mp_midden_dir="$COLONY_DATA_DIR/midden"
|
|
966
|
+
mp_merges_file="$mp_midden_dir/collected-merges.json"
|
|
967
|
+
mp_midden_file="$mp_midden_dir/midden.json"
|
|
968
|
+
mp_pruned_merges=0
|
|
969
|
+
mp_pruned_reverted=0
|
|
970
|
+
|
|
971
|
+
if [[ "$mp_stale_merges" == "true" ]]; then
|
|
972
|
+
if [[ -f "$mp_merges_file" ]]; then
|
|
973
|
+
mp_cutoff=$(date -u -v-"90d" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
|
974
|
+
python3 -c "import datetime; print((datetime.datetime.utcnow() - datetime.timedelta(days=90)).strftime('%Y-%m-%dT%H:%M:%SZ'))" 2>/dev/null || \
|
|
975
|
+
date -u -d "90 days ago" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
976
|
+
|
|
977
|
+
if [[ -n "$mp_cutoff" ]]; then
|
|
978
|
+
acquire_lock "$mp_merges_file" || true
|
|
979
|
+
trap 'release_lock 2>/dev/null || true' EXIT
|
|
980
|
+
|
|
981
|
+
mp_before=$(jq '.merges | length' "$mp_merges_file" 2>/dev/null || echo "0")
|
|
982
|
+
mp_before=${mp_before:-0}
|
|
983
|
+
mp_merges_updated=$(jq --arg cutoff "$mp_cutoff" \
|
|
984
|
+
'.merges = [.merges // [] | .[] | select(.collected_at >= $cutoff)]' \
|
|
985
|
+
"$mp_merges_file" 2>/dev/null)
|
|
986
|
+
mp_after=$(echo "$mp_merges_updated" | jq '.merges | length' 2>/dev/null || echo "0")
|
|
987
|
+
mp_after=${mp_after:-0}
|
|
988
|
+
|
|
989
|
+
if [[ -n "$mp_merges_updated" ]]; then
|
|
990
|
+
atomic_write "$mp_merges_file" "$mp_merges_updated"
|
|
991
|
+
mp_pruned_merges=$((mp_before - mp_after))
|
|
992
|
+
fi
|
|
993
|
+
|
|
994
|
+
trap - EXIT
|
|
995
|
+
release_lock 2>/dev/null || true
|
|
996
|
+
fi
|
|
997
|
+
fi
|
|
998
|
+
fi
|
|
999
|
+
|
|
1000
|
+
if [[ "$mp_reverted" == "true" ]]; then
|
|
1001
|
+
if [[ -f "$mp_midden_file" && -f "$mp_merges_file" ]]; then
|
|
1002
|
+
mp_cutoff=$(date -u -v-"${mp_age}d" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || \
|
|
1003
|
+
python3 -c "import datetime; print((datetime.datetime.utcnow() - datetime.timedelta(days=$mp_age)).strftime('%Y-%m-%dT%H:%M:%SZ'))" 2>/dev/null || \
|
|
1004
|
+
date -u -d "$mp_age days ago" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
1005
|
+
|
|
1006
|
+
if [[ -n "$mp_cutoff" ]]; then
|
|
1007
|
+
# Find revert timestamps from collected-merges.json
|
|
1008
|
+
mp_revert_map=$(jq -r '[.merges // [] | .[] | select(.status == "reverted")] | map({merge_commit: .merge_commit, reverted_at: .reverted_at})' "$mp_merges_file" 2>/dev/null || echo "[]")
|
|
1009
|
+
|
|
1010
|
+
acquire_lock "$mp_midden_file" || true
|
|
1011
|
+
trap 'release_lock 2>/dev/null || true' EXIT
|
|
1012
|
+
|
|
1013
|
+
mp_now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1014
|
+
|
|
1015
|
+
# Acknowledge reverted entries older than threshold
|
|
1016
|
+
mp_updated=$(jq --argjson revert_map "$mp_revert_map" \
|
|
1017
|
+
--arg cutoff "$mp_cutoff" --arg now "$mp_now" --argjson age "$mp_age" \
|
|
1018
|
+
'
|
|
1019
|
+
.entries = [.entries // [] | .[] |
|
|
1020
|
+
if .tags // [] | map(startswith("reverted:")) | any then
|
|
1021
|
+
# Find the revert timestamp for this entry
|
|
1022
|
+
($revert_map | map(select(.merge_commit == .merge_commit)) | first // {}) as $merge_info |
|
|
1023
|
+
if ($merge_info.reverted_at // "") != "" and ($merge_info.reverted_at < $cutoff) and .acknowledged != true then
|
|
1024
|
+
. + {
|
|
1025
|
+
acknowledged: true,
|
|
1026
|
+
acknowledged_at: $now,
|
|
1027
|
+
acknowledge_reason: ("auto-pruned: reverted entry older than " + ($age | tostring) + " days")
|
|
1028
|
+
}
|
|
1029
|
+
else .
|
|
1030
|
+
end
|
|
1031
|
+
else .
|
|
1032
|
+
end
|
|
1033
|
+
]
|
|
1034
|
+
' "$mp_midden_file" 2>/dev/null)
|
|
1035
|
+
|
|
1036
|
+
if [[ -n "$mp_updated" ]]; then
|
|
1037
|
+
mp_before=$(jq '[.entries // [] | .[] | select(.tags // [] | map(startswith("reverted:")) | any and .acknowledged != true)] | length' "$mp_midden_file" 2>/dev/null || echo "0")
|
|
1038
|
+
mp_before=${mp_before:-0}
|
|
1039
|
+
mp_after=$(jq '[.entries // [] | .[] | select(.tags // [] | map(startswith("reverted:")) | any and .acknowledged != true)] | length' <<< "$mp_updated" 2>/dev/null || echo "0")
|
|
1040
|
+
mp_after=${mp_after:-0}
|
|
1041
|
+
atomic_write "$mp_midden_file" "$mp_updated"
|
|
1042
|
+
mp_pruned_reverted=$((mp_before - mp_after))
|
|
1043
|
+
fi
|
|
1044
|
+
|
|
1045
|
+
trap - EXIT
|
|
1046
|
+
release_lock 2>/dev/null || true
|
|
1047
|
+
fi
|
|
1048
|
+
fi
|
|
1049
|
+
fi
|
|
1050
|
+
|
|
1051
|
+
json_ok "$(jq -n --argjson pruned_merges "$mp_pruned_merges" --argjson pruned_reverted "$mp_pruned_reverted" \
|
|
1052
|
+
'{pruned_merges: $pruned_merges, pruned_reverted: $pruned_reverted}')"
|
|
1053
|
+
return 0
|
|
1054
|
+
}
|