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
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Council deliberation module — Advocate/Challenger/Sage model with spawn budget guards
|
|
3
|
+
# Provides: _council_deliberate, _council_advocate, _council_challenger, _council_sage,
|
|
4
|
+
# _council_history, _council_budget_check
|
|
5
|
+
#
|
|
6
|
+
# These functions are sourced by aether-utils.sh at startup.
|
|
7
|
+
# All shared infrastructure (json_ok, json_err, atomic_write, acquire_lock,
|
|
8
|
+
# release_lock, LOCK_DIR, COLONY_DATA_DIR, error constants) is available.
|
|
9
|
+
# _spawn_can_spawn is available from spawn.sh (sourced before this module).
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# Internal helpers
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
_council_data_dir() {
|
|
16
|
+
echo "$COLONY_DATA_DIR/council"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_council_deliberations_file() {
|
|
20
|
+
echo "$COLONY_DATA_DIR/council/deliberations.json"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_council_ensure_file() {
|
|
24
|
+
local cf
|
|
25
|
+
cf="$(_council_deliberations_file)"
|
|
26
|
+
mkdir -p "$(_council_data_dir)"
|
|
27
|
+
if [[ ! -f "$cf" ]]; then
|
|
28
|
+
printf '%s\n' '{"version":"1.0","deliberations":[]}' > "$cf"
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# _council_deliberate
|
|
34
|
+
# Usage: council-deliberate --proposal <text> [--budget N] [--depth light|standard|deep]
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
_council_deliberate() {
|
|
37
|
+
local cd_proposal=""
|
|
38
|
+
local cd_budget="3"
|
|
39
|
+
local cd_depth="standard"
|
|
40
|
+
|
|
41
|
+
while [[ $# -gt 0 ]]; do
|
|
42
|
+
case "$1" in
|
|
43
|
+
--proposal) cd_proposal="${2:-}"; shift 2 ;;
|
|
44
|
+
--budget) cd_budget="${2:-3}"; shift 2 ;;
|
|
45
|
+
--depth) cd_depth="${2:-standard}"; shift 2 ;;
|
|
46
|
+
*) shift ;;
|
|
47
|
+
esac
|
|
48
|
+
done
|
|
49
|
+
|
|
50
|
+
if [[ -z "$cd_proposal" ]]; then
|
|
51
|
+
json_err "$E_VALIDATION_FAILED" "council-deliberate requires --proposal"
|
|
52
|
+
return
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
if ! [[ "$cd_budget" =~ ^[0-9]+$ ]]; then
|
|
56
|
+
json_err "$E_VALIDATION_FAILED" "council-deliberate --budget must be a positive integer"
|
|
57
|
+
return
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
local cd_ts
|
|
61
|
+
cd_ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
62
|
+
local cd_unix
|
|
63
|
+
cd_unix=$(date -u +%s)
|
|
64
|
+
local cd_id="delib_${cd_unix}"
|
|
65
|
+
|
|
66
|
+
_council_ensure_file
|
|
67
|
+
|
|
68
|
+
local cd_file
|
|
69
|
+
cd_file="$(_council_deliberations_file)"
|
|
70
|
+
|
|
71
|
+
acquire_lock "$cd_file" 2>/dev/null || true
|
|
72
|
+
# shellcheck disable=SC2064
|
|
73
|
+
trap "release_lock '$cd_file' 2>/dev/null || true" EXIT
|
|
74
|
+
|
|
75
|
+
local cd_updated
|
|
76
|
+
cd_updated=$(jq \
|
|
77
|
+
--arg id "$cd_id" \
|
|
78
|
+
--arg proposal "$cd_proposal" \
|
|
79
|
+
--arg ts "$cd_ts" \
|
|
80
|
+
--argjson budget "$cd_budget" \
|
|
81
|
+
--arg depth "$cd_depth" \
|
|
82
|
+
'.deliberations += [{
|
|
83
|
+
"id": $id,
|
|
84
|
+
"proposal": $proposal,
|
|
85
|
+
"advocate": null,
|
|
86
|
+
"challenger": null,
|
|
87
|
+
"sage": null,
|
|
88
|
+
"budget": $budget,
|
|
89
|
+
"depth": $depth,
|
|
90
|
+
"created_at": $ts,
|
|
91
|
+
"status": "pending"
|
|
92
|
+
}]' "$cd_file") || {
|
|
93
|
+
release_lock "$cd_file" 2>/dev/null || true
|
|
94
|
+
trap - EXIT
|
|
95
|
+
json_err "$E_UNKNOWN" "Failed to write deliberation"
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
atomic_write "$cd_file" "$cd_updated" || {
|
|
100
|
+
release_lock "$cd_file" 2>/dev/null || true
|
|
101
|
+
trap - EXIT
|
|
102
|
+
json_err "$E_UNKNOWN" "Failed to persist deliberation"
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
release_lock "$cd_file" 2>/dev/null || true
|
|
107
|
+
trap - EXIT
|
|
108
|
+
|
|
109
|
+
json_ok "$(jq -n \
|
|
110
|
+
--arg id "$cd_id" \
|
|
111
|
+
--arg proposal "$cd_proposal" \
|
|
112
|
+
--argjson budget "$cd_budget" \
|
|
113
|
+
'{"id":$id,"proposal":$proposal,"status":"pending","budget":$budget}')"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# _council_advocate
|
|
118
|
+
# Usage: council-advocate --deliberation-id <id> --argument <text>
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
_council_advocate() {
|
|
121
|
+
local ca_id=""
|
|
122
|
+
local ca_argument=""
|
|
123
|
+
|
|
124
|
+
while [[ $# -gt 0 ]]; do
|
|
125
|
+
case "$1" in
|
|
126
|
+
--deliberation-id) ca_id="${2:-}"; shift 2 ;;
|
|
127
|
+
--argument) ca_argument="${2:-}"; shift 2 ;;
|
|
128
|
+
*) shift ;;
|
|
129
|
+
esac
|
|
130
|
+
done
|
|
131
|
+
|
|
132
|
+
if [[ -z "$ca_id" ]]; then
|
|
133
|
+
json_err "$E_VALIDATION_FAILED" "council-advocate requires --deliberation-id"
|
|
134
|
+
return
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [[ -z "$ca_argument" ]]; then
|
|
138
|
+
json_err "$E_VALIDATION_FAILED" "council-advocate requires --argument"
|
|
139
|
+
return
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
local ca_file
|
|
143
|
+
ca_file="$(_council_deliberations_file)"
|
|
144
|
+
|
|
145
|
+
if [[ ! -f "$ca_file" ]]; then
|
|
146
|
+
json_err "$E_VALIDATION_FAILED" "No deliberations found; run council-deliberate first"
|
|
147
|
+
return
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
local ca_exists
|
|
151
|
+
ca_exists=$(jq -r --arg id "$ca_id" '.deliberations[] | select(.id == $id) | .id' "$ca_file" 2>/dev/null || echo "")
|
|
152
|
+
if [[ -z "$ca_exists" ]]; then
|
|
153
|
+
json_err "$E_VALIDATION_FAILED" "Deliberation not found: $ca_id"
|
|
154
|
+
return
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
acquire_lock "$ca_file" 2>/dev/null || true
|
|
158
|
+
# shellcheck disable=SC2064
|
|
159
|
+
trap "release_lock '$ca_file' 2>/dev/null || true" EXIT
|
|
160
|
+
|
|
161
|
+
local ca_updated
|
|
162
|
+
ca_updated=$(jq \
|
|
163
|
+
--arg id "$ca_id" \
|
|
164
|
+
--arg arg "$ca_argument" \
|
|
165
|
+
'(.deliberations[] | select(.id == $id)).advocate = $arg
|
|
166
|
+
| (.deliberations[] | select(.id == $id)).status = "in_progress"' \
|
|
167
|
+
"$ca_file") || {
|
|
168
|
+
release_lock "$ca_file" 2>/dev/null || true
|
|
169
|
+
trap - EXIT
|
|
170
|
+
json_err "$E_UNKNOWN" "Failed to record advocate argument"
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
atomic_write "$ca_file" "$ca_updated" || {
|
|
175
|
+
release_lock "$ca_file" 2>/dev/null || true
|
|
176
|
+
trap - EXIT
|
|
177
|
+
json_err "$E_UNKNOWN" "Failed to persist advocate argument"
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
release_lock "$ca_file" 2>/dev/null || true
|
|
182
|
+
trap - EXIT
|
|
183
|
+
|
|
184
|
+
json_ok '{"role":"advocate","recorded":true}'
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# _council_challenger
|
|
189
|
+
# Usage: council-challenger --deliberation-id <id> --argument <text>
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
_council_challenger() {
|
|
192
|
+
local cc_id=""
|
|
193
|
+
local cc_argument=""
|
|
194
|
+
|
|
195
|
+
while [[ $# -gt 0 ]]; do
|
|
196
|
+
case "$1" in
|
|
197
|
+
--deliberation-id) cc_id="${2:-}"; shift 2 ;;
|
|
198
|
+
--argument) cc_argument="${2:-}"; shift 2 ;;
|
|
199
|
+
*) shift ;;
|
|
200
|
+
esac
|
|
201
|
+
done
|
|
202
|
+
|
|
203
|
+
if [[ -z "$cc_id" ]]; then
|
|
204
|
+
json_err "$E_VALIDATION_FAILED" "council-challenger requires --deliberation-id"
|
|
205
|
+
return
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
if [[ -z "$cc_argument" ]]; then
|
|
209
|
+
json_err "$E_VALIDATION_FAILED" "council-challenger requires --argument"
|
|
210
|
+
return
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
local cc_file
|
|
214
|
+
cc_file="$(_council_deliberations_file)"
|
|
215
|
+
|
|
216
|
+
if [[ ! -f "$cc_file" ]]; then
|
|
217
|
+
json_err "$E_VALIDATION_FAILED" "No deliberations found; run council-deliberate first"
|
|
218
|
+
return
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
local cc_exists
|
|
222
|
+
cc_exists=$(jq -r --arg id "$cc_id" '.deliberations[] | select(.id == $id) | .id' "$cc_file" 2>/dev/null || echo "")
|
|
223
|
+
if [[ -z "$cc_exists" ]]; then
|
|
224
|
+
json_err "$E_VALIDATION_FAILED" "Deliberation not found: $cc_id"
|
|
225
|
+
return
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
acquire_lock "$cc_file" 2>/dev/null || true
|
|
229
|
+
# shellcheck disable=SC2064
|
|
230
|
+
trap "release_lock '$cc_file' 2>/dev/null || true" EXIT
|
|
231
|
+
|
|
232
|
+
local cc_updated
|
|
233
|
+
cc_updated=$(jq \
|
|
234
|
+
--arg id "$cc_id" \
|
|
235
|
+
--arg arg "$cc_argument" \
|
|
236
|
+
'(.deliberations[] | select(.id == $id)).challenger = $arg
|
|
237
|
+
| (.deliberations[] | select(.id == $id)).status = "in_progress"' \
|
|
238
|
+
"$cc_file") || {
|
|
239
|
+
release_lock "$cc_file" 2>/dev/null || true
|
|
240
|
+
trap - EXIT
|
|
241
|
+
json_err "$E_UNKNOWN" "Failed to record challenger argument"
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
atomic_write "$cc_file" "$cc_updated" || {
|
|
246
|
+
release_lock "$cc_file" 2>/dev/null || true
|
|
247
|
+
trap - EXIT
|
|
248
|
+
json_err "$E_UNKNOWN" "Failed to persist challenger argument"
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
release_lock "$cc_file" 2>/dev/null || true
|
|
253
|
+
trap - EXIT
|
|
254
|
+
|
|
255
|
+
json_ok '{"role":"challenger","recorded":true}'
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
# _council_sage
|
|
260
|
+
# Usage: council-sage --deliberation-id <id> --synthesis <text> --recommendation <text>
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
_council_sage() {
|
|
263
|
+
local cs_id=""
|
|
264
|
+
local cs_synthesis=""
|
|
265
|
+
local cs_recommendation=""
|
|
266
|
+
|
|
267
|
+
while [[ $# -gt 0 ]]; do
|
|
268
|
+
case "$1" in
|
|
269
|
+
--deliberation-id) cs_id="${2:-}"; shift 2 ;;
|
|
270
|
+
--synthesis) cs_synthesis="${2:-}"; shift 2 ;;
|
|
271
|
+
--recommendation) cs_recommendation="${2:-}"; shift 2 ;;
|
|
272
|
+
*) shift ;;
|
|
273
|
+
esac
|
|
274
|
+
done
|
|
275
|
+
|
|
276
|
+
if [[ -z "$cs_id" ]]; then
|
|
277
|
+
json_err "$E_VALIDATION_FAILED" "council-sage requires --deliberation-id"
|
|
278
|
+
return
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
if [[ -z "$cs_synthesis" ]]; then
|
|
282
|
+
json_err "$E_VALIDATION_FAILED" "council-sage requires --synthesis"
|
|
283
|
+
return
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
if [[ -z "$cs_recommendation" ]]; then
|
|
287
|
+
json_err "$E_VALIDATION_FAILED" "council-sage requires --recommendation"
|
|
288
|
+
return
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
local cs_file
|
|
292
|
+
cs_file="$(_council_deliberations_file)"
|
|
293
|
+
|
|
294
|
+
if [[ ! -f "$cs_file" ]]; then
|
|
295
|
+
json_err "$E_VALIDATION_FAILED" "No deliberations found; run council-deliberate first"
|
|
296
|
+
return
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
local cs_exists
|
|
300
|
+
cs_exists=$(jq -r --arg id "$cs_id" '.deliberations[] | select(.id == $id) | .id' "$cs_file" 2>/dev/null || echo "")
|
|
301
|
+
if [[ -z "$cs_exists" ]]; then
|
|
302
|
+
json_err "$E_VALIDATION_FAILED" "Deliberation not found: $cs_id"
|
|
303
|
+
return
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
acquire_lock "$cs_file" 2>/dev/null || true
|
|
307
|
+
# shellcheck disable=SC2064
|
|
308
|
+
trap "release_lock '$cs_file' 2>/dev/null || true" EXIT
|
|
309
|
+
|
|
310
|
+
local cs_updated
|
|
311
|
+
cs_updated=$(jq \
|
|
312
|
+
--arg id "$cs_id" \
|
|
313
|
+
--arg synthesis "$cs_synthesis" \
|
|
314
|
+
--arg rec "$cs_recommendation" \
|
|
315
|
+
'(.deliberations[] | select(.id == $id)).sage = {"synthesis": $synthesis, "recommendation": $rec}
|
|
316
|
+
| (.deliberations[] | select(.id == $id)).status = "complete"' \
|
|
317
|
+
"$cs_file") || {
|
|
318
|
+
release_lock "$cs_file" 2>/dev/null || true
|
|
319
|
+
trap - EXIT
|
|
320
|
+
json_err "$E_UNKNOWN" "Failed to record sage synthesis"
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
atomic_write "$cs_file" "$cs_updated" || {
|
|
325
|
+
release_lock "$cs_file" 2>/dev/null || true
|
|
326
|
+
trap - EXIT
|
|
327
|
+
json_err "$E_UNKNOWN" "Failed to persist sage synthesis"
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
release_lock "$cs_file" 2>/dev/null || true
|
|
332
|
+
trap - EXIT
|
|
333
|
+
|
|
334
|
+
json_ok "$(jq -n \
|
|
335
|
+
--arg rec "$cs_recommendation" \
|
|
336
|
+
'{"role":"sage","recommendation":$rec,"deliberation_complete":true}')"
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
# ---------------------------------------------------------------------------
|
|
340
|
+
# _council_history
|
|
341
|
+
# Usage: council-history [--limit N]
|
|
342
|
+
# ---------------------------------------------------------------------------
|
|
343
|
+
_council_history() {
|
|
344
|
+
local ch_limit=""
|
|
345
|
+
|
|
346
|
+
while [[ $# -gt 0 ]]; do
|
|
347
|
+
case "$1" in
|
|
348
|
+
--limit) ch_limit="${2:-}"; shift 2 ;;
|
|
349
|
+
*) shift ;;
|
|
350
|
+
esac
|
|
351
|
+
done
|
|
352
|
+
|
|
353
|
+
local ch_file
|
|
354
|
+
ch_file="$(_council_deliberations_file)"
|
|
355
|
+
|
|
356
|
+
if [[ ! -f "$ch_file" ]]; then
|
|
357
|
+
json_ok '{"total":0,"deliberations":[]}'
|
|
358
|
+
return
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
local ch_total
|
|
362
|
+
ch_total=$(jq '.deliberations | length' "$ch_file" 2>/dev/null || echo 0)
|
|
363
|
+
|
|
364
|
+
if [[ -n "$ch_limit" ]] && [[ "$ch_limit" =~ ^[0-9]+$ ]]; then
|
|
365
|
+
local ch_result
|
|
366
|
+
ch_result=$(jq \
|
|
367
|
+
--argjson limit "$ch_limit" \
|
|
368
|
+
--argjson total "$ch_total" \
|
|
369
|
+
'{"total":$total,"deliberations":(.deliberations | .[-($limit):])}' \
|
|
370
|
+
"$ch_file" 2>/dev/null) || ch_result='{"total":0,"deliberations":[]}'
|
|
371
|
+
json_ok "$ch_result"
|
|
372
|
+
else
|
|
373
|
+
local ch_result
|
|
374
|
+
ch_result=$(jq \
|
|
375
|
+
--argjson total "$ch_total" \
|
|
376
|
+
'{"total":$total,"deliberations":.deliberations}' \
|
|
377
|
+
"$ch_file" 2>/dev/null) || ch_result='{"total":0,"deliberations":[]}'
|
|
378
|
+
json_ok "$ch_result"
|
|
379
|
+
fi
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
# ---------------------------------------------------------------------------
|
|
383
|
+
# _council_budget_check
|
|
384
|
+
# Usage: council-budget-check [--budget N]
|
|
385
|
+
# ---------------------------------------------------------------------------
|
|
386
|
+
_council_budget_check() {
|
|
387
|
+
local cb_budget="3"
|
|
388
|
+
|
|
389
|
+
while [[ $# -gt 0 ]]; do
|
|
390
|
+
case "$1" in
|
|
391
|
+
--budget) cb_budget="${2:-3}"; shift 2 ;;
|
|
392
|
+
*) shift ;;
|
|
393
|
+
esac
|
|
394
|
+
done
|
|
395
|
+
|
|
396
|
+
if ! [[ "$cb_budget" =~ ^[0-9]+$ ]]; then
|
|
397
|
+
json_err "$E_VALIDATION_FAILED" "council-budget-check --budget must be a positive integer"
|
|
398
|
+
return
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
# Delegate to spawn-can-spawn at depth 1
|
|
402
|
+
local cb_spawn_result
|
|
403
|
+
cb_spawn_result=$(_spawn_can_spawn 1 2>/dev/null || echo '{"can_spawn":false,"current_total":0,"global_cap":10}')
|
|
404
|
+
|
|
405
|
+
local cb_can
|
|
406
|
+
cb_can=$(echo "$cb_spawn_result" | jq -r '.result.can_spawn // .can_spawn // false' 2>/dev/null || echo "false")
|
|
407
|
+
local cb_current
|
|
408
|
+
cb_current=$(echo "$cb_spawn_result" | jq -r '.result.current_total // .current_total // 0' 2>/dev/null || echo 0)
|
|
409
|
+
local cb_cap
|
|
410
|
+
cb_cap=$(echo "$cb_spawn_result" | jq -r '.result.global_cap // .global_cap // 10' 2>/dev/null || echo 10)
|
|
411
|
+
local cb_remaining=$(( cb_cap - cb_current ))
|
|
412
|
+
[[ $cb_remaining -lt 0 ]] && cb_remaining=0
|
|
413
|
+
|
|
414
|
+
# allowed is true only if spawn is allowed AND remaining >= requested budget
|
|
415
|
+
local cb_allowed="false"
|
|
416
|
+
if [[ "$cb_can" == "true" ]] && [[ $cb_remaining -ge $cb_budget ]]; then
|
|
417
|
+
cb_allowed="true"
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
json_ok "$(jq -n \
|
|
421
|
+
--argjson allowed "$cb_allowed" \
|
|
422
|
+
--argjson remaining "$cb_remaining" \
|
|
423
|
+
--argjson budget "$cb_budget" \
|
|
424
|
+
'{"allowed":$allowed,"remaining":$remaining,"budget":$budget}')"
|
|
425
|
+
}
|
|
@@ -87,7 +87,7 @@ json_err() {
|
|
|
87
87
|
"$code" "$escaped_message" "$details_json" "$recovery" "$timestamp" >&2
|
|
88
88
|
|
|
89
89
|
# Log to activity.log (best effort)
|
|
90
|
-
if [[ -n "${COLONY_DATA_DIR:-}" ]]; then
|
|
90
|
+
if [[ -n "${COLONY_DATA_DIR:-}" ]] && [[ "${AETHER_TESTING:-}" != "1" ]]; then
|
|
91
91
|
echo "[$timestamp] ERROR $code: $escaped_message" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
|
|
92
92
|
fi
|
|
93
93
|
|
|
@@ -111,7 +111,7 @@ json_warn() {
|
|
|
111
111
|
"$code" "$escaped_message" "$timestamp"
|
|
112
112
|
|
|
113
113
|
# Log to activity.log (best effort)
|
|
114
|
-
if [[ -n "${COLONY_DATA_DIR:-}" ]]; then
|
|
114
|
+
if [[ -n "${COLONY_DATA_DIR:-}" ]] && [[ "${AETHER_TESTING:-}" != "1" ]]; then
|
|
115
115
|
echo "[$timestamp] WARN $code: $escaped_message" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
|
|
116
116
|
fi
|
|
117
117
|
}
|
|
@@ -153,7 +153,7 @@ error_handler() {
|
|
|
153
153
|
"$E_BASH_ERROR" "$details" "$(_recovery_default)" "$timestamp" >&2
|
|
154
154
|
|
|
155
155
|
# Log to activity.log (best effort)
|
|
156
|
-
if [[ -n "${COLONY_DATA_DIR:-}" ]]; then
|
|
156
|
+
if [[ -n "${COLONY_DATA_DIR:-}" ]] && [[ "${AETHER_TESTING:-}" != "1" ]]; then
|
|
157
157
|
echo "[$timestamp] ERROR $E_BASH_ERROR: Command failed at line $line_num (exit $exit_code)" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
|
|
158
158
|
fi
|
|
159
159
|
|
package/.aether/utils/flag.sh
CHANGED
|
@@ -204,22 +204,33 @@ _flag_list() {
|
|
|
204
204
|
exit 0
|
|
205
205
|
fi
|
|
206
206
|
|
|
207
|
-
#
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
if [[ -n "$filter_type" ]]; then
|
|
215
|
-
jq_filter+=" | [.[] | select(.type == \"$filter_type\")]"
|
|
207
|
+
# Validate filter_phase as numeric (safe for --argjson)
|
|
208
|
+
if [[ -n "$filter_phase" && "$filter_phase" =~ ^[0-9]+$ ]]; then
|
|
209
|
+
phase_num="$filter_phase"
|
|
210
|
+
else
|
|
211
|
+
phase_num=""
|
|
216
212
|
fi
|
|
217
213
|
|
|
218
|
-
|
|
219
|
-
|
|
214
|
+
# Build jq command with --arg to prevent filter injection
|
|
215
|
+
# filter_type is passed via --arg (safe string interpolation in jq)
|
|
216
|
+
# phase_num is passed via --argjson (numeric-only, pre-validated)
|
|
217
|
+
if [[ -n "$phase_num" ]]; then
|
|
218
|
+
result=$(jq --arg ft "$filter_type" --argjson ph "$phase_num" --argjson all "$show_all" '
|
|
219
|
+
.flags
|
|
220
|
+
| (if $all | not then [.[] | select(.resolved_at == null)] else . end)
|
|
221
|
+
| (if $ft != "" then [.[] | select(.type == $ft)] else . end)
|
|
222
|
+
| (if $ph != null then [.[] | select(.phase == $ph or .phase == null)] else . end)
|
|
223
|
+
| {flags: ., count: length}
|
|
224
|
+
' "$flags_file")
|
|
225
|
+
else
|
|
226
|
+
result=$(jq --arg ft "$filter_type" --argjson all "$show_all" '
|
|
227
|
+
.flags
|
|
228
|
+
| (if $all | not then [.[] | select(.resolved_at == null)] else . end)
|
|
229
|
+
| (if $ft != "" then [.[] | select(.type == $ft)] else . end)
|
|
230
|
+
| {flags: ., count: length}
|
|
231
|
+
' "$flags_file")
|
|
220
232
|
fi
|
|
221
233
|
|
|
222
|
-
result=$(jq "{flags: ($jq_filter), count: ($jq_filter | length)}" "$flags_file")
|
|
223
234
|
json_ok "$result"
|
|
224
235
|
}
|
|
225
236
|
|
package/.aether/utils/hive.sh
CHANGED
|
@@ -306,14 +306,14 @@ _hive_read() {
|
|
|
306
306
|
--argjson limit "$hr_limit" '
|
|
307
307
|
.entries
|
|
308
308
|
| map(
|
|
309
|
-
select((.confidence | tonumber) >= $min_conf)
|
|
309
|
+
select(((.confidence // 0) | tonumber) >= $min_conf)
|
|
310
310
|
| if ($domain_filter | length) > 0 then
|
|
311
311
|
select(
|
|
312
312
|
[.domain_tags[] as $dt | $domain_filter[] | select(. == $dt)] | length > 0
|
|
313
313
|
)
|
|
314
314
|
else . end
|
|
315
315
|
)
|
|
316
|
-
| sort_by(-(.confidence | tonumber), -.validated_count)
|
|
316
|
+
| sort_by(-((.confidence // 0) | tonumber), -.validated_count)
|
|
317
317
|
| { total_matched: length, entries: .[:$limit], returned_ids: [.[:$limit][].id] }
|
|
318
318
|
' "$hr_wisdom_file" 2>/dev/null) || {
|
|
319
319
|
json_ok '{"entries":[],"total_matched":0,"fallback":"filter_error"}'
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Clash Detection PreToolUse Hook
|
|
3
|
+
//
|
|
4
|
+
// Runs before Edit/Write tool calls in Claude Code.
|
|
5
|
+
// Checks if any other active git worktree has uncommitted changes to the
|
|
6
|
+
// same file, preventing agents from silently overwriting each other's work.
|
|
7
|
+
//
|
|
8
|
+
// Exit codes:
|
|
9
|
+
// 0 - Allow the operation
|
|
10
|
+
// 2 - Block the operation (conflict detected)
|
|
11
|
+
//
|
|
12
|
+
// Design principle: fail-open. If the hook errors for any reason,
|
|
13
|
+
// the operation is allowed. The goal is safety guidance, not blocking work.
|
|
14
|
+
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// Files that are branch-local state and never clash across worktrees
|
|
19
|
+
const ALLOWLIST = [
|
|
20
|
+
'.aether/data/',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Timeout for clash-detect subprocess (ms)
|
|
24
|
+
const DETECT_TIMEOUT = 5000;
|
|
25
|
+
|
|
26
|
+
let input = '';
|
|
27
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
28
|
+
process.stdin.setEncoding('utf8');
|
|
29
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
30
|
+
process.stdin.on('end', () => {
|
|
31
|
+
clearTimeout(stdinTimeout);
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(input);
|
|
34
|
+
const toolName = data.tool_name;
|
|
35
|
+
|
|
36
|
+
// Only check Edit and Write operations
|
|
37
|
+
if (toolName !== 'Edit' && toolName !== 'Write') {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extract file path
|
|
42
|
+
const filePath = data.tool_input?.file_path || '';
|
|
43
|
+
if (!filePath) {
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check allowlist (branch-local state files never clash)
|
|
48
|
+
if (ALLOWLIST.some(pattern => filePath.includes(pattern))) {
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Find the clash-detect script
|
|
53
|
+
// It lives at .aether/utils/clash-detect.sh relative to the repo root
|
|
54
|
+
const cwd = data.cwd || process.cwd();
|
|
55
|
+
const repoClashDetect = path.join(cwd, '.aether', 'utils', 'clash-detect.sh');
|
|
56
|
+
|
|
57
|
+
// Extract just the relative file path for clash-detect
|
|
58
|
+
const relPath = path.relative(cwd, filePath);
|
|
59
|
+
|
|
60
|
+
// Determine which clash-detect to use: repo-local or PATH
|
|
61
|
+
const fs = require('fs');
|
|
62
|
+
const clashDetect = fs.existsSync(repoClashDetect) ? repoClashDetect : 'clash-detect';
|
|
63
|
+
|
|
64
|
+
// Run clash-detect
|
|
65
|
+
try {
|
|
66
|
+
const cmd = clashDetect === 'clash-detect'
|
|
67
|
+
? `clash-detect --file "${relPath}"`
|
|
68
|
+
: `bash "${clashDetect}" --file "${relPath}"`;
|
|
69
|
+
const result = execSync(cmd, {
|
|
70
|
+
timeout: DETECT_TIMEOUT,
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
cwd: cwd,
|
|
73
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const parsed = JSON.parse(result.trim());
|
|
77
|
+
if (parsed.ok && parsed.result?.conflict) {
|
|
78
|
+
const worktrees = (parsed.result.conflicting_worktrees || []).join(', ');
|
|
79
|
+
const output = {
|
|
80
|
+
decision: 'block',
|
|
81
|
+
reason: `File "${relPath}" has uncommitted changes in worktree(s): ${worktrees}. ` +
|
|
82
|
+
'Coordinate with the other agent or wait for their changes to be committed.',
|
|
83
|
+
};
|
|
84
|
+
process.stderr.write(JSON.stringify(output) + '\n');
|
|
85
|
+
process.exit(2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// No conflict -- allow
|
|
89
|
+
process.exit(0);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
// Fail-open: if clash-detect fails, allow the operation
|
|
92
|
+
// This prevents the hook from blocking all work when something goes wrong
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
// Fail-open on any parsing or unexpected error
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
});
|