aether-colony 5.3.3 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.aether/commands/archaeology.yaml +3 -3
  2. package/.aether/commands/build.yaml +45 -45
  3. package/.aether/commands/chaos.yaml +7 -7
  4. package/.aether/commands/colonize.yaml +17 -17
  5. package/.aether/commands/continue.yaml +40 -40
  6. package/.aether/commands/council.yaml +6 -6
  7. package/.aether/commands/data-clean.yaml +3 -3
  8. package/.aether/commands/dream.yaml +2 -2
  9. package/.aether/commands/entomb.yaml +11 -11
  10. package/.aether/commands/export-signals.yaml +2 -2
  11. package/.aether/commands/feedback.yaml +6 -6
  12. package/.aether/commands/flag.yaml +2 -2
  13. package/.aether/commands/flags.yaml +4 -4
  14. package/.aether/commands/focus.yaml +6 -6
  15. package/.aether/commands/help.yaml +1 -1
  16. package/.aether/commands/history.yaml +1 -1
  17. package/.aether/commands/import-signals.yaml +2 -2
  18. package/.aether/commands/init.yaml +16 -16
  19. package/.aether/commands/insert-phase.yaml +1 -1
  20. package/.aether/commands/interpret.yaml +2 -2
  21. package/.aether/commands/lay-eggs.yaml +3 -3
  22. package/.aether/commands/maturity.yaml +2 -2
  23. package/.aether/commands/memory-details.yaml +1 -1
  24. package/.aether/commands/migrate-state.yaml +1 -1
  25. package/.aether/commands/oracle.yaml +77 -82
  26. package/.aether/commands/organize.yaml +5 -5
  27. package/.aether/commands/patrol.yaml +6 -6
  28. package/.aether/commands/pause-colony.yaml +7 -7
  29. package/.aether/commands/phase.yaml +1 -1
  30. package/.aether/commands/pheromones.yaml +1 -1
  31. package/.aether/commands/plan.yaml +14 -14
  32. package/.aether/commands/quick.yaml +4 -4
  33. package/.aether/commands/redirect.yaml +6 -6
  34. package/.aether/commands/resume-colony.yaml +9 -9
  35. package/.aether/commands/resume.yaml +5 -38
  36. package/.aether/commands/run.yaml +7 -7
  37. package/.aether/commands/seal.yaml +33 -33
  38. package/.aether/commands/skill-create.yaml +4 -4
  39. package/.aether/commands/status.yaml +14 -14
  40. package/.aether/commands/swarm.yaml +13 -13
  41. package/.aether/commands/tunnels.yaml +7 -7
  42. package/.aether/commands/update.yaml +1 -1
  43. package/.aether/commands/verify-castes.yaml +3 -3
  44. package/.aether/commands/watch.yaml +15 -15
  45. package/.aether/docs/command-playbooks/build-complete.md +7 -7
  46. package/.aether/docs/command-playbooks/build-context.md +11 -11
  47. package/.aether/docs/command-playbooks/build-full.md +69 -69
  48. package/.aether/docs/command-playbooks/build-prep.md +9 -9
  49. package/.aether/docs/command-playbooks/build-verify.md +27 -27
  50. package/.aether/docs/command-playbooks/build-wave.md +38 -38
  51. package/.aether/docs/command-playbooks/continue-advance.md +28 -28
  52. package/.aether/docs/command-playbooks/continue-finalize.md +12 -12
  53. package/.aether/docs/command-playbooks/continue-full.md +47 -47
  54. package/.aether/docs/command-playbooks/continue-gates.md +18 -18
  55. package/.aether/docs/command-playbooks/continue-verify.md +10 -10
  56. package/.aether/templates/colony-state-template.json +1 -0
  57. package/.aether/utils/oracle/oracle-stop-hook.sh +896 -0
  58. package/.claude/commands/ant/archaeology.md +2 -2
  59. package/.claude/commands/ant/chaos.md +4 -4
  60. package/.claude/commands/ant/colonize.md +9 -9
  61. package/.claude/commands/ant/council.md +6 -6
  62. package/.claude/commands/ant/data-clean.md +3 -3
  63. package/.claude/commands/ant/dream.md +2 -2
  64. package/.claude/commands/ant/entomb.md +8 -8
  65. package/.claude/commands/ant/export-signals.md +2 -2
  66. package/.claude/commands/ant/feedback.md +4 -4
  67. package/.claude/commands/ant/flag.md +2 -2
  68. package/.claude/commands/ant/flags.md +4 -4
  69. package/.claude/commands/ant/focus.md +4 -4
  70. package/.claude/commands/ant/help.md +1 -1
  71. package/.claude/commands/ant/history.md +1 -1
  72. package/.claude/commands/ant/import-signals.md +2 -2
  73. package/.claude/commands/ant/init.md +16 -16
  74. package/.claude/commands/ant/insert-phase.md +1 -1
  75. package/.claude/commands/ant/interpret.md +2 -2
  76. package/.claude/commands/ant/lay-eggs.md +2 -2
  77. package/.claude/commands/ant/maturity.md +2 -2
  78. package/.claude/commands/ant/memory-details.md +1 -1
  79. package/.claude/commands/ant/migrate-state.md +1 -1
  80. package/.claude/commands/ant/oracle.md +43 -42
  81. package/.claude/commands/ant/organize.md +3 -3
  82. package/.claude/commands/ant/patrol.md +6 -6
  83. package/.claude/commands/ant/pause-colony.md +5 -5
  84. package/.claude/commands/ant/phase.md +1 -1
  85. package/.claude/commands/ant/pheromones.md +1 -1
  86. package/.claude/commands/ant/plan.md +8 -8
  87. package/.claude/commands/ant/quick.md +4 -4
  88. package/.claude/commands/ant/redirect.md +4 -4
  89. package/.claude/commands/ant/resume-colony.md +5 -5
  90. package/.claude/commands/ant/resume.md +17 -29
  91. package/.claude/commands/ant/run.md +7 -7
  92. package/.claude/commands/ant/seal.md +25 -25
  93. package/.claude/commands/ant/skill-create.md +2 -2
  94. package/.claude/commands/ant/status.md +14 -14
  95. package/.claude/commands/ant/swarm.md +13 -13
  96. package/.claude/commands/ant/tunnels.md +4 -4
  97. package/.claude/commands/ant/update.md +1 -1
  98. package/.claude/commands/ant/verify-castes.md +2 -2
  99. package/.claude/commands/ant/watch.md +8 -8
  100. package/.opencode/commands/ant/archaeology.md +1 -1
  101. package/.opencode/commands/ant/build.md +45 -45
  102. package/.opencode/commands/ant/chaos.md +3 -3
  103. package/.opencode/commands/ant/colonize.md +8 -8
  104. package/.opencode/commands/ant/continue.md +40 -40
  105. package/.opencode/commands/ant/council.md +5 -5
  106. package/.opencode/commands/ant/data-clean.md +2 -2
  107. package/.opencode/commands/ant/dream.md +1 -1
  108. package/.opencode/commands/ant/entomb.md +3 -3
  109. package/.opencode/commands/ant/export-signals.md +1 -1
  110. package/.opencode/commands/ant/feedback.md +2 -2
  111. package/.opencode/commands/ant/flag.md +1 -1
  112. package/.opencode/commands/ant/flags.md +3 -3
  113. package/.opencode/commands/ant/focus.md +2 -2
  114. package/.opencode/commands/ant/import-signals.md +1 -1
  115. package/.opencode/commands/ant/init.md +16 -16
  116. package/.opencode/commands/ant/insert-phase.md +1 -1
  117. package/.opencode/commands/ant/interpret.md +1 -1
  118. package/.opencode/commands/ant/lay-eggs.md +2 -2
  119. package/.opencode/commands/ant/maturity.md +1 -1
  120. package/.opencode/commands/ant/memory-details.md +1 -1
  121. package/.opencode/commands/ant/oracle.md +34 -40
  122. package/.opencode/commands/ant/organize.md +2 -2
  123. package/.opencode/commands/ant/patrol.md +6 -6
  124. package/.opencode/commands/ant/pause-colony.md +2 -2
  125. package/.opencode/commands/ant/pheromones.md +1 -1
  126. package/.opencode/commands/ant/plan.md +6 -6
  127. package/.opencode/commands/ant/quick.md +4 -4
  128. package/.opencode/commands/ant/redirect.md +2 -2
  129. package/.opencode/commands/ant/resume-colony.md +4 -4
  130. package/.opencode/commands/ant/resume.md +5 -17
  131. package/.opencode/commands/ant/run.md +7 -7
  132. package/.opencode/commands/ant/seal.md +8 -8
  133. package/.opencode/commands/ant/skill-create.md +2 -2
  134. package/.opencode/commands/ant/status.md +10 -10
  135. package/.opencode/commands/ant/tunnels.md +3 -3
  136. package/.opencode/commands/ant/verify-castes.md +1 -1
  137. package/.opencode/commands/ant/watch.md +7 -7
  138. package/bin/cli.js +118 -3
  139. package/bin/lib/binary-downloader.js +267 -0
  140. package/bin/lib/update-transaction.js +19 -0
  141. package/bin/lib/version-gate.js +179 -0
  142. package/package.json +1 -1
@@ -0,0 +1,896 @@
1
+ #!/bin/bash
2
+ # Oracle Ant - In-Session Stop Hook
3
+ # Intercepts session exit when Oracle research is active.
4
+ # Re-feeds phase-aware prompts to continue the research loop.
5
+ #
6
+ # Modeled on the Ralph Loop Stop hook pattern:
7
+ # - Checks .aether/oracle/.loop-active for loop state
8
+ # - Session isolation via session_id
9
+ # - Outputs {"decision":"block","reason":"..."} to re-feed prompt
10
+ # - Outputs nothing (exit 0) to allow normal stop
11
+ #
12
+ # Completion criteria (any triggers synthesis pass):
13
+ # - Max iterations reached
14
+ # - overall_confidence >= target_confidence
15
+ # - <oracle>COMPLETE</oracle> in last assistant message
16
+ # - Convergence detected (composite score >= threshold + low novelty)
17
+
18
+ set -euo pipefail
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Configuration
22
+ # ---------------------------------------------------------------------------
23
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+ AETHER_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
25
+ ORACLE_DIR="$AETHER_ROOT/.aether/oracle"
26
+ MARKER_FILE="$ORACLE_DIR/.loop-active"
27
+ STATE_FILE="$ORACLE_DIR/state.json"
28
+ PLAN_FILE="$ORACLE_DIR/plan.json"
29
+ STOP_FILE="$ORACLE_DIR/.stop"
30
+ ORACLE_MD="$SCRIPT_DIR/oracle.md"
31
+
32
+ # Convergence thresholds (matching oracle.sh defaults)
33
+ CONV_THRESHOLD=${ORACLE_CONVERGENCE_THRESHOLD:-85}
34
+ DR_WINDOW=${ORACLE_DR_WINDOW:-3}
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Fast exit: no active loop
38
+ # ---------------------------------------------------------------------------
39
+ if [[ ! -f "$MARKER_FILE" ]]; then
40
+ exit 0
41
+ fi
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # Read hook input from stdin
45
+ # ---------------------------------------------------------------------------
46
+ HOOK_INPUT=$(cat)
47
+
48
+ INPUT_SESSION_ID=$(echo "$HOOK_INPUT" | jq -r '.session_id // ""')
49
+ TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path // ""')
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # Parse marker file frontmatter
53
+ # ---------------------------------------------------------------------------
54
+ FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$MARKER_FILE")
55
+
56
+ ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
57
+ MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//')
58
+ MARKER_SESSION_ID=$(echo "$FRONTMATTER" | grep '^session_id:' | sed 's/session_id: *//' || true)
59
+ CURRENT_PHASE=$(echo "$FRONTMATTER" | grep '^phase:' | sed 's/phase: *//' || echo "survey")
60
+ TARGET_CONFIDENCE=$(echo "$FRONTMATTER" | grep '^target_confidence:' | sed 's/target_confidence: *//' || echo "95")
61
+ SYNTHESIS_DONE=$(echo "$FRONTMATTER" | grep '^synthesis_done:' | sed 's/synthesis_done: *//' || echo "false")
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Validate numeric fields
65
+ # ---------------------------------------------------------------------------
66
+ if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then
67
+ echo "Oracle hook: Invalid iteration in marker file. Removing." >&2
68
+ rm -f "$MARKER_FILE"
69
+ exit 0
70
+ fi
71
+
72
+ if [[ ! "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then
73
+ echo "Oracle hook: Invalid max_iterations in marker file. Removing." >&2
74
+ rm -f "$MARKER_FILE"
75
+ exit 0
76
+ fi
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Session isolation
80
+ # ---------------------------------------------------------------------------
81
+ if [[ -n "$MARKER_SESSION_ID" ]] && [[ "$MARKER_SESSION_ID" != "$INPUT_SESSION_ID" ]]; then
82
+ # Different session started this loop -- don't interfere
83
+ exit 0
84
+ fi
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # Check for user-requested stop
88
+ # ---------------------------------------------------------------------------
89
+ if [[ -f "$STOP_FILE" ]]; then
90
+ rm -f "$STOP_FILE"
91
+ rm -f "$MARKER_FILE"
92
+ echo "Oracle hook: Stop signal received. Ending research." >&2
93
+ exit 0
94
+ fi
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # Validate state files exist
98
+ # ---------------------------------------------------------------------------
99
+ if [[ ! -f "$STATE_FILE" ]]; then
100
+ echo "Oracle hook: state.json missing. Ending research." >&2
101
+ rm -f "$MARKER_FILE"
102
+ exit 0
103
+ fi
104
+
105
+ # Validate state.json is valid JSON
106
+ if ! jq -e . "$STATE_FILE" >/dev/null 2>&1; then
107
+ echo "Oracle hook: state.json is corrupt. Ending research." >&2
108
+ rm -f "$MARKER_FILE"
109
+ exit 0
110
+ fi
111
+
112
+ # ---------------------------------------------------------------------------
113
+ # Read state from state.json
114
+ # ---------------------------------------------------------------------------
115
+ OVERALL_CONFIDENCE=$(jq '.overall_confidence // 0' "$STATE_FILE" 2>/dev/null || echo "0")
116
+ STATE_TARGET=$(jq '.target_confidence // 95' "$STATE_FILE" 2>/dev/null || echo "95")
117
+ # Prefer marker's target_confidence, fall back to state.json
118
+ TARGET=${TARGET_CONFIDENCE:-$STATE_TARGET}
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # Read last assistant message from transcript for COMPLETE tag
122
+ # ---------------------------------------------------------------------------
123
+ COMPLETE_TAG_FOUND=false
124
+ if [[ -n "$TRANSCRIPT_PATH" ]] && [[ -f "$TRANSCRIPT_PATH" ]]; then
125
+ # Get last 100 assistant lines (bounded for performance)
126
+ LAST_LINES=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -n 100 2>/dev/null || echo "")
127
+ if [[ -n "$LAST_LINES" ]]; then
128
+ LAST_OUTPUT=$(echo "$LAST_LINES" | jq -rs '
129
+ map(.message.content[]? | select(.type == "text") | .text) | last // ""
130
+ ' 2>/dev/null || echo "")
131
+ if echo "$LAST_OUTPUT" | grep -q "<oracle>COMPLETE</oracle>"; then
132
+ COMPLETE_TAG_FOUND=true
133
+ fi
134
+ fi
135
+ fi
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # Convergence detection (ported from oracle.sh)
139
+ # ---------------------------------------------------------------------------
140
+
141
+ # Compute convergence metrics from plan.json
142
+ compute_convergence() {
143
+ local plan_file="$1"
144
+ local state_file="$2"
145
+
146
+ [[ -f "$plan_file" ]] || { echo '{"gap_resolution_pct":0,"coverage_pct":0,"novelty_delta":0,"total_findings":0}'; return 0; }
147
+
148
+ local total answered partial_high
149
+ total=$(jq '[.questions[]] | length' "$plan_file" 2>/dev/null || echo "0")
150
+ answered=$(jq '[.questions[] | select(.status == "answered")] | length' "$plan_file" 2>/dev/null || echo "0")
151
+ partial_high=$(jq '[.questions[] | select(.status == "partial" and .confidence >= 70)] | length' "$plan_file" 2>/dev/null || echo "0")
152
+
153
+ local gap_resolution=0
154
+ if [[ "$total" -gt 0 ]]; then
155
+ gap_resolution=$(( (answered + partial_high) * 100 / total ))
156
+ fi
157
+
158
+ local touched coverage=0
159
+ touched=$(jq '[.questions[] | select((.iterations_touched // []) | length > 0)] | length' "$plan_file" 2>/dev/null || echo "0")
160
+ if [[ "$total" -gt 0 ]]; then
161
+ coverage=$(( touched * 100 / total ))
162
+ fi
163
+
164
+ local current_findings prev_findings novelty_delta
165
+ current_findings=$(jq '[.questions[].key_findings | length] | add // 0' "$plan_file" 2>/dev/null || echo "0")
166
+ prev_findings=$(jq '.convergence.prev_findings_count // 0' "$state_file" 2>/dev/null || echo "0")
167
+ novelty_delta=$(( current_findings - prev_findings ))
168
+
169
+ jq -n --argjson gap "$gap_resolution" --argjson cov "$coverage" \
170
+ --argjson novelty "$novelty_delta" --argjson findings "$current_findings" \
171
+ '{gap_resolution_pct: $gap, coverage_pct: $cov, novelty_delta: $novelty, total_findings: $findings}'
172
+ }
173
+
174
+ # Check if research has converged
175
+ check_convergence() {
176
+ local state_file="$1"
177
+
178
+ local composite_score
179
+ composite_score=$(jq '.convergence.composite_score // 0' "$state_file" 2>/dev/null || echo "0")
180
+
181
+ if [[ "$composite_score" -lt "$CONV_THRESHOLD" ]]; then
182
+ return 1
183
+ fi
184
+
185
+ local history_len
186
+ history_len=$(jq '(.convergence.history // []) | length' "$state_file" 2>/dev/null || echo "0")
187
+ if [[ "$history_len" -lt 2 ]]; then
188
+ return 1
189
+ fi
190
+
191
+ local low_novelty_count
192
+ low_novelty_count=$(jq '[(.convergence.history // [])[-2:][] | select(.novelty_delta <= 1)] | length' "$state_file" 2>/dev/null || echo "0")
193
+
194
+ [[ "$low_novelty_count" -ge 2 ]]
195
+ }
196
+
197
+ # Detect diminishing returns
198
+ detect_diminishing_returns() {
199
+ local state_file="$1"
200
+
201
+ local history_len
202
+ history_len=$(jq '(.convergence.history // []) | length' "$state_file" 2>/dev/null || echo "0")
203
+
204
+ if [[ "$history_len" -lt "$DR_WINDOW" ]]; then
205
+ echo "continue"
206
+ return 0
207
+ fi
208
+
209
+ local novelty_threshold
210
+ case "$CURRENT_PHASE" in
211
+ investigate) novelty_threshold=0 ;;
212
+ *) novelty_threshold=1 ;;
213
+ esac
214
+
215
+ local low_change_count
216
+ low_change_count=$(jq --argjson window "$DR_WINDOW" --argjson threshold "$novelty_threshold" \
217
+ '[(.convergence.history // [])[-$window:][] | select(.novelty_delta <= $threshold)] | length' \
218
+ "$state_file" 2>/dev/null || echo "0")
219
+
220
+ if [[ "$low_change_count" -ge "$DR_WINDOW" ]]; then
221
+ case "$CURRENT_PHASE" in
222
+ survey|investigate) echo "strategy_change" ;;
223
+ synthesize|verify) echo "synthesize_now" ;;
224
+ *) echo "continue" ;;
225
+ esac
226
+ else
227
+ echo "continue"
228
+ fi
229
+ }
230
+
231
+ # Update convergence metrics in state.json
232
+ update_convergence_metrics() {
233
+ local state_file="$1"
234
+ local plan_file="$2"
235
+ local next_iteration="$3"
236
+ local next_phase="$4"
237
+
238
+ local metrics
239
+ metrics=$(compute_convergence "$plan_file" "$state_file")
240
+
241
+ local gap_pct coverage_pct novelty_delta total_findings
242
+ gap_pct=$(echo "$metrics" | jq '.gap_resolution_pct')
243
+ coverage_pct=$(echo "$metrics" | jq '.coverage_pct')
244
+ novelty_delta=$(echo "$metrics" | jq '.novelty_delta')
245
+ total_findings=$(echo "$metrics" | jq '.total_findings')
246
+
247
+ local current_confidence prev_confidence confidence_delta
248
+ current_confidence=$(jq '.overall_confidence // 0' "$state_file" 2>/dev/null || echo "0")
249
+ prev_confidence=$(jq '.convergence.prev_overall_confidence // 0' "$state_file" 2>/dev/null || echo "0")
250
+ confidence_delta=$(( current_confidence - prev_confidence ))
251
+
252
+ # Composite score: gap*0.4 + coverage*0.3 + (novelty<=1?100:0)*0.3
253
+ local novelty_component composite_score converged
254
+ if [[ "$novelty_delta" -le 1 ]]; then
255
+ novelty_component=100
256
+ else
257
+ novelty_component=0
258
+ fi
259
+ composite_score=$(( gap_pct * 40 / 100 + coverage_pct * 30 / 100 + novelty_component * 30 / 100 ))
260
+
261
+ if [[ "$composite_score" -ge "$CONV_THRESHOLD" ]]; then
262
+ converged="true"
263
+ else
264
+ converged="false"
265
+ fi
266
+
267
+ # Atomic write
268
+ jq --argjson prev_findings "$total_findings" \
269
+ --argjson prev_confidence "$current_confidence" \
270
+ --argjson iteration "$next_iteration" \
271
+ --argjson novelty "$novelty_delta" \
272
+ --argjson conf_delta "$confidence_delta" \
273
+ --argjson gap "$gap_pct" \
274
+ --argjson cov "$coverage_pct" \
275
+ --arg phase "$next_phase" \
276
+ --argjson composite "$composite_score" \
277
+ --argjson converged "$converged" \
278
+ '
279
+ .convergence = (.convergence // {}) |
280
+ .convergence.prev_findings_count = $prev_findings |
281
+ .convergence.prev_overall_confidence = $prev_confidence |
282
+ .convergence.history = ((.convergence.history // []) + [{
283
+ iteration: $iteration,
284
+ novelty_delta: $novelty,
285
+ confidence_delta: $conf_delta,
286
+ gap_resolution_pct: $gap,
287
+ coverage_pct: $cov,
288
+ phase: $phase
289
+ }]) |
290
+ .convergence.composite_score = $composite |
291
+ .convergence.converged = $converged
292
+ ' "$state_file" > "$state_file.tmp" && mv "$state_file.tmp" "$state_file"
293
+ }
294
+
295
+ # Compute trust scores from plan.json source tracking
296
+ compute_trust_scores() {
297
+ local plan_file="$1"
298
+
299
+ [[ -f "$plan_file" ]] || return 0
300
+
301
+ local has_structured
302
+ has_structured=$(jq '
303
+ [.questions[].key_findings[] | type] | if length == 0 then false else any(. == "object") end
304
+ ' "$plan_file" 2>/dev/null || echo "false")
305
+
306
+ if [[ "$has_structured" != "true" ]]; then
307
+ return 0
308
+ fi
309
+
310
+ local total_findings single_source multi_source
311
+ total_findings=$(jq '[.questions[].key_findings[]] | length' "$plan_file" 2>/dev/null || echo "0")
312
+ single_source=$(jq '[.questions[].key_findings[] | select(type == "object" and (.source_ids | length) == 1)] | length' "$plan_file" 2>/dev/null || echo "0")
313
+ multi_source=$(jq '[.questions[].key_findings[] | select(type == "object" and (.source_ids | length) >= 2)] | length' "$plan_file" 2>/dev/null || echo "0")
314
+ local no_source
315
+ no_source=$(jq '[.questions[].key_findings[] | select(type == "object" and ((.source_ids // []) | length) == 0)] | length' "$plan_file" 2>/dev/null || echo "0")
316
+
317
+ local trust_ratio=0
318
+ if [[ "$total_findings" -gt 0 ]]; then
319
+ trust_ratio=$(( multi_source * 100 / total_findings ))
320
+ fi
321
+
322
+ jq --argjson total "$total_findings" \
323
+ --argjson single "$single_source" \
324
+ --argjson multi "$multi_source" \
325
+ --argjson nosrc "$no_source" \
326
+ --argjson ratio "$trust_ratio" \
327
+ '.trust_summary = {
328
+ total_findings: $total,
329
+ single_source: $single,
330
+ multi_source: $multi,
331
+ no_source: $nosrc,
332
+ trust_ratio: $ratio
333
+ }' "$plan_file" > "$plan_file.tmp" && mv "$plan_file.tmp" "$plan_file"
334
+ }
335
+
336
+ # ---------------------------------------------------------------------------
337
+ # Determine next phase (ported from oracle.sh)
338
+ # ---------------------------------------------------------------------------
339
+ determine_next_phase() {
340
+ local state_file="$1"
341
+ local plan_file="$2"
342
+
343
+ [[ -f "$state_file" ]] || { echo "survey"; return 0; }
344
+ [[ -f "$plan_file" ]] || { echo "survey"; return 0; }
345
+
346
+ local total_questions touched_count avg_confidence below_50_count
347
+
348
+ total_questions=$(jq '[.questions[]] | length' "$plan_file" 2>/dev/null || echo "0")
349
+ if [[ "$total_questions" -eq 0 ]]; then
350
+ echo "survey"
351
+ return 0
352
+ fi
353
+
354
+ touched_count=$(jq '[.questions[] | select((.iterations_touched // []) | length > 0)] | length' "$plan_file" 2>/dev/null || echo "0")
355
+ avg_confidence=$(jq '[.questions[].confidence] | if length > 0 then (add / length) else 0 end | floor' "$plan_file" 2>/dev/null || echo "0")
356
+ below_50_count=$(jq '[.questions[] | select(.status != "answered" and .confidence < 50)] | length' "$plan_file" 2>/dev/null || echo "0")
357
+
358
+ if [[ "$avg_confidence" -ge 80 ]]; then
359
+ echo "verify"
360
+ return 0
361
+ fi
362
+
363
+ if [[ "$avg_confidence" -ge 60 ]] || [[ "$below_50_count" -lt 2 ]]; then
364
+ echo "synthesize"
365
+ return 0
366
+ fi
367
+
368
+ if [[ "$touched_count" -ge "$total_questions" ]] || [[ "$avg_confidence" -ge 25 ]]; then
369
+ echo "investigate"
370
+ return 0
371
+ fi
372
+
373
+ echo "survey"
374
+ }
375
+
376
+ # ---------------------------------------------------------------------------
377
+ # Phase directive generators (copied verbatim from oracle.sh)
378
+ # ---------------------------------------------------------------------------
379
+ phase_directive_survey() {
380
+ cat <<'DIRECTIVE'
381
+ ## Current Phase: SURVEY
382
+
383
+ Cast a wide net -- get initial findings for every open question. Target untouched
384
+ questions first (those with empty iterations_touched arrays). Aim for 20-40%
385
+ confidence per question. List all discovered unknowns in gaps.md.
386
+
387
+ Do NOT go deep on any single question yet. Breadth over depth in this phase.
388
+ Your goal is to ensure every question has at least some initial findings before
389
+ the research moves to the investigation phase.
390
+
391
+ Source tracking is MANDATORY -- register sources and link every finding to source_ids.
392
+
393
+ ---
394
+
395
+ DIRECTIVE
396
+ }
397
+
398
+ phase_directive_investigate() {
399
+ cat <<'DIRECTIVE'
400
+ ## Current Phase: INVESTIGATE
401
+
402
+ Target the lowest-confidence question and go DEEP. You MUST reference existing
403
+ findings in synthesis.md and ADD NEW information, not restate what is already there.
404
+ Aim to push confidence above 70% for your target question.
405
+
406
+ Update gaps.md with specific remaining unknowns. If you find contradictions with
407
+ existing findings, document them explicitly. One thoroughly investigated question
408
+ per iteration is better than shallow passes on many.
409
+
410
+ Source tracking is MANDATORY this iteration. Every new finding must have at least one source_id.
411
+
412
+ ---
413
+
414
+ DIRECTIVE
415
+ }
416
+
417
+ phase_directive_synthesize() {
418
+ cat <<'DIRECTIVE'
419
+ ## Current Phase: SYNTHESIZE
420
+
421
+ Read ALL findings in synthesis.md before doing anything. Identify connections,
422
+ patterns, and contradictions ACROSS questions. Consolidate redundant findings.
423
+ Resolve contradictions with evidence. Push overall confidence toward the target.
424
+
425
+ Your job is NOT to find new information -- it is to make sense of what has already
426
+ been found. Cross-reference answers between questions. Strengthen weak claims
427
+ with evidence from other questions. Remove speculation that lacks support.
428
+
429
+ Verify source attribution is complete. Flag any findings missing source_ids.
430
+
431
+ ---
432
+
433
+ DIRECTIVE
434
+ }
435
+
436
+ phase_directive_verify() {
437
+ cat <<'DIRECTIVE'
438
+ ## Current Phase: VERIFY
439
+
440
+ Focus on claims in gaps.md contradictions section. Cross-reference key findings
441
+ with additional sources. Confirm or correct confidence scores. Mark well-supported
442
+ questions as answered with 90%+ confidence.
443
+
444
+ Final gaps.md should contain only genuinely unresolvable unknowns. If a contradiction
445
+ cannot be resolved, document both positions with evidence quality assessment.
446
+ This is the final quality pass before research completion.
447
+
448
+ Cross-reference source coverage. Ensure all key findings have 2+ independent sources.
449
+
450
+ ---
451
+
452
+ DIRECTIVE
453
+ }
454
+
455
+ phase_directive_for() {
456
+ local phase="$1"
457
+ case "$phase" in
458
+ survey) phase_directive_survey ;;
459
+ investigate) phase_directive_investigate ;;
460
+ synthesize) phase_directive_synthesize ;;
461
+ verify) phase_directive_verify ;;
462
+ *) echo "## Current Phase: $phase"; echo ""; echo "---"; echo "" ;;
463
+ esac
464
+ }
465
+
466
+ # ---------------------------------------------------------------------------
467
+ # Strategy modifier (ported from oracle.sh)
468
+ # ---------------------------------------------------------------------------
469
+ strategy_modifier() {
470
+ local state_file="$1"
471
+ local strategy
472
+ strategy=$(jq -r '.strategy // "adaptive"' "$state_file" 2>/dev/null || echo "adaptive")
473
+
474
+ case "$strategy" in
475
+ breadth-first)
476
+ cat <<'STRATEGY'
477
+
478
+ STRATEGY NOTE: Breadth-first mode is active. Prioritize covering ALL questions
479
+ before going deep on any single one. Aim for broad coverage across the research
480
+ plan. When multiple questions are untouched, target the easiest-to-answer first
481
+ for maximum coverage.
482
+
483
+ STRATEGY
484
+ ;;
485
+ depth-first)
486
+ cat <<'STRATEGY'
487
+
488
+ STRATEGY NOTE: Depth-first mode is active. Pick the single most important open
489
+ question and investigate it exhaustively. Push confidence to 80%+ before moving
490
+ to the next question. Prefer thorough, well-sourced answers over broad coverage.
491
+
492
+ STRATEGY
493
+ ;;
494
+ *) ;; # adaptive: no modifier
495
+ esac
496
+ }
497
+
498
+ # ---------------------------------------------------------------------------
499
+ # Steering signals (ported from oracle.sh)
500
+ # ---------------------------------------------------------------------------
501
+ read_steering_signals() {
502
+ local utils="$AETHER_ROOT/.aether/aether-utils.sh"
503
+
504
+ [[ -f "$utils" ]] || return 0
505
+
506
+ local signals
507
+ signals=$(bash "$utils" pheromone-read 2>/dev/null || echo '{"signals":[]}')
508
+
509
+ local signal_array
510
+ signal_array=$(echo "$signals" | jq -c '.result.signals // .signals // []' 2>/dev/null || echo '[]')
511
+
512
+ local count
513
+ count=$(echo "$signal_array" | jq 'length' 2>/dev/null || echo "0")
514
+
515
+ [[ "$count" -gt 0 ]] || return 0
516
+
517
+ local directive=""
518
+
519
+ # REDIRECT (max 2)
520
+ local redirects
521
+ redirects=$(echo "$signal_array" | jq -r '
522
+ map(select(.type == "REDIRECT"))
523
+ | sort_by(-.effective_strength)
524
+ | .[:2]
525
+ | .[] | "- [" + ((.effective_strength * 100 | floor | tostring)) + "%] \"" + (.content.text // "") + "\""
526
+ ' 2>/dev/null || echo "")
527
+
528
+ if [[ -n "$redirects" ]]; then
529
+ directive+="**REDIRECT (Hard constraints -- MUST follow):**"$'\n'"$redirects"$'\n\n'
530
+ fi
531
+
532
+ # FOCUS (max 3)
533
+ local focuses
534
+ focuses=$(echo "$signal_array" | jq -r '
535
+ map(select(.type == "FOCUS"))
536
+ | sort_by(-.effective_strength)
537
+ | .[:3]
538
+ | .[] | "- [" + ((.effective_strength * 100 | floor | tostring)) + "%] \"" + (.content.text // "") + "\""
539
+ ' 2>/dev/null || echo "")
540
+
541
+ if [[ -n "$focuses" ]]; then
542
+ directive+="**FOCUS (Prioritize these areas):**"$'\n'"$focuses"$'\n\n'
543
+ fi
544
+
545
+ # FEEDBACK (max 2)
546
+ local feedbacks
547
+ feedbacks=$(echo "$signal_array" | jq -r '
548
+ map(select(.type == "FEEDBACK"))
549
+ | sort_by(-.effective_strength)
550
+ | .[:2]
551
+ | .[] | "- [" + ((.effective_strength * 100 | floor | tostring)) + "%] \"" + (.content.text // "") + "\""
552
+ ' 2>/dev/null || echo "")
553
+
554
+ if [[ -n "$feedbacks" ]]; then
555
+ directive+="**FEEDBACK (Adjust approach):**"$'\n'"$feedbacks"$'\n\n'
556
+ fi
557
+
558
+ if [[ -n "$directive" ]]; then
559
+ echo "## Active Steering Signals"
560
+ echo ""
561
+ echo "$directive"
562
+ echo "When selecting your target question, PRIORITIZE questions related to FOCUS areas."
563
+ echo "REDIRECT signals are hard constraints -- adjust your approach to comply."
564
+ echo "FEEDBACK signals are suggestions -- incorporate where appropriate."
565
+ echo ""
566
+ echo "---"
567
+ echo ""
568
+ fi
569
+ }
570
+
571
+ # ---------------------------------------------------------------------------
572
+ # Synthesis prompt builder (ported from oracle.sh)
573
+ # ---------------------------------------------------------------------------
574
+ build_synthesis_prompt() {
575
+ local reason="$1"
576
+ local template
577
+ template=$(jq -r '.template // "custom"' "$STATE_FILE" 2>/dev/null || echo "custom")
578
+
579
+ cat <<SYNTHESIS_DIRECTIVE
580
+ ## SYNTHESIS PASS (Final Report)
581
+
582
+ This is the final pass. The oracle loop has ended (reason: $reason).
583
+ Produce the best possible research report from the current state.
584
+
585
+ Read ALL of these files:
586
+ - .aether/oracle/state.json -- session metadata
587
+ - .aether/oracle/plan.json -- questions, findings, confidence, AND sources registry
588
+ - .aether/oracle/synthesis.md -- accumulated findings
589
+ - .aether/oracle/gaps.md -- remaining unknowns
590
+
591
+ If any state file is unreadable, skip it and work with what you have.
592
+
593
+ Then REWRITE synthesis.md as a structured final report.
594
+
595
+ SYNTHESIS_DIRECTIVE
596
+
597
+ # Template-specific sections
598
+ case "$template" in
599
+ tech-eval)
600
+ cat <<'TEMPLATE'
601
+ ### Required Sections:
602
+ 1. **Executive Summary** -- 2-3 paragraphs: what was evaluated, key conclusion, recommendation
603
+ 2. **Comparison Matrix** -- Table comparing the evaluated technology against alternatives on key dimensions
604
+ 3. **Pros and Cons** -- Bullet lists with evidence citations
605
+ 4. **Adoption Assessment** -- Community size, maintenance status, release cadence
606
+ 5. **Migration/Integration Path** -- Steps to adopt, estimated effort, risks
607
+ 6. **Recommendation** -- Clear recommendation with confidence level
608
+ 7. **Open Questions** -- Remaining gaps
609
+ 8. **Sources** -- All sources with inline citations [S1], [S2] format
610
+
611
+ TEMPLATE
612
+ ;;
613
+ architecture-review)
614
+ cat <<'TEMPLATE'
615
+ ### Required Sections:
616
+ 1. **Executive Summary** -- 2-3 paragraphs: system overview, key findings, critical risks
617
+ 2. **Component Map** -- List of major components with responsibilities
618
+ 3. **Dependency Analysis** -- How components connect, coupling assessment
619
+ 4. **Risk Assessment** -- Single points of failure, complexity hotspots
620
+ 5. **Scalability Analysis** -- Current capacity, growth limitations
621
+ 6. **Improvement Recommendations** -- Prioritized list
622
+ 7. **Open Questions** -- Remaining gaps
623
+ 8. **Sources** -- All sources with inline citations [S1], [S2] format
624
+
625
+ TEMPLATE
626
+ ;;
627
+ bug-investigation)
628
+ cat <<'TEMPLATE'
629
+ ### Required Sections:
630
+ 1. **Executive Summary** -- 1-2 paragraphs: bug description, root cause, recommended fix
631
+ 2. **Reproduction Steps** -- Exact steps to reproduce
632
+ 3. **Root Cause Analysis** -- What causes the bug, code paths involved
633
+ 4. **Impact Assessment** -- Who is affected, severity
634
+ 5. **Fix Recommendations** -- Proposed fixes ranked by safety and effort
635
+ 6. **Related Issues** -- Similar bugs, regression risk
636
+ 7. **Open Questions** -- Remaining gaps
637
+ 8. **Sources** -- All sources with inline citations [S1], [S2] format
638
+
639
+ TEMPLATE
640
+ ;;
641
+ best-practices)
642
+ cat <<'TEMPLATE'
643
+ ### Required Sections:
644
+ 1. **Executive Summary** -- 2-3 paragraphs: domain overview, key recommendations
645
+ 2. **Best Practice Benchmark** -- What industry consensus considers best practice
646
+ 3. **Current State Assessment** -- How the subject compares
647
+ 4. **Gap Analysis** -- Specific gaps prioritized by impact
648
+ 5. **Action Plan** -- Ordered steps to close gaps
649
+ 6. **Open Questions** -- Remaining gaps
650
+ 7. **Sources** -- All sources with inline citations [S1], [S2] format
651
+
652
+ TEMPLATE
653
+ ;;
654
+ *)
655
+ cat <<'TEMPLATE'
656
+ ### Required Sections:
657
+ 1. **Executive Summary** -- 2-3 paragraphs summarizing what was found
658
+ 2. **Findings by Question** -- organized by sub-question, with confidence %
659
+ 3. **Open Questions** -- remaining gaps
660
+ 4. **Methodology Notes** -- how many iterations, which phases completed
661
+ 5. **Sources** -- List ALL sources from plan.json sources registry
662
+
663
+ TEMPLATE
664
+ ;;
665
+ esac
666
+
667
+ # Common directives
668
+ cat <<'COMMON'
669
+
670
+ ### Confidence Grouping:
671
+ Within each findings section, group findings by confidence level:
672
+ - **High confidence (80%+)** -- list first with full citations
673
+ - **Medium confidence (50-79%)** -- list with caveats
674
+ - **Low confidence (<50%)** -- list as tentative/unverified
675
+
676
+ Use inline citations [S1], [S2] linking findings to their sources.
677
+ Flag single-source findings with (single source) marker.
678
+
679
+ Also update state.json: set status to "complete" if reason is "converged",
680
+ or "stopped" otherwise.
681
+
682
+ COMMON
683
+
684
+ # Append the base oracle.md for tool access and rules
685
+ cat "$ORACLE_MD"
686
+ }
687
+
688
+ # ---------------------------------------------------------------------------
689
+ # Update marker file with new values
690
+ # ---------------------------------------------------------------------------
691
+ update_marker() {
692
+ local new_iteration="$1"
693
+ local new_phase="$2"
694
+ local new_synthesis_done="$3"
695
+
696
+ local oracle_md_body
697
+ oracle_md_body=$(awk '/^---$/{i++; next} i>=2' "$MARKER_FILE")
698
+
699
+ cat > "${MARKER_FILE}.tmp.$$" <<MARKER
700
+ ---
701
+ iteration: $new_iteration
702
+ max_iterations: $MAX_ITERATIONS
703
+ session_id: $MARKER_SESSION_ID
704
+ phase: $new_phase
705
+ target_confidence: $TARGET_CONFIDENCE
706
+ synthesis_done: $new_synthesis_done
707
+ oracle_md_path: .aether/utils/oracle/oracle.md
708
+ ---
709
+ $oracle_md_body
710
+ MARKER
711
+ mv "${MARKER_FILE}.tmp.$$" "$MARKER_FILE"
712
+ }
713
+
714
+ # ---------------------------------------------------------------------------
715
+ # Update state.json iteration and phase
716
+ # ---------------------------------------------------------------------------
717
+ update_state_json() {
718
+ local new_iteration="$1"
719
+ local new_phase="$2"
720
+
721
+ local ts
722
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
723
+
724
+ jq --argjson iter "$new_iteration" \
725
+ --arg phase "$new_phase" \
726
+ --arg ts "$ts" \
727
+ '.iteration = $iter | .phase = $phase | .last_updated = $ts' \
728
+ "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
729
+ }
730
+
731
+ # ---------------------------------------------------------------------------
732
+ # MAIN DECISION LOGIC
733
+ # ---------------------------------------------------------------------------
734
+
735
+ NEXT_ITERATION=$((ITERATION + 1))
736
+
737
+ # Check completion conditions
738
+ IS_COMPLETE=false
739
+ COMPLETION_REASON=""
740
+
741
+ # 1. Max iterations reached
742
+ if [[ "$MAX_ITERATIONS" -gt 0 ]] && [[ "$NEXT_ITERATION" -gt "$MAX_ITERATIONS" ]]; then
743
+ IS_COMPLETE=true
744
+ COMPLETION_REASON="max_iterations"
745
+ fi
746
+
747
+ # 2. Confidence target met
748
+ if [[ "$OVERALL_CONFIDENCE" -ge "$TARGET" ]]; then
749
+ IS_COMPLETE=true
750
+ COMPLETION_REASON="${COMPLETION_REASON:-confidence_target}"
751
+ fi
752
+
753
+ # 3. AI signaled complete
754
+ if [[ "$COMPLETE_TAG_FOUND" == "true" ]]; then
755
+ IS_COMPLETE=true
756
+ COMPLETION_REASON="${COMPLETION_REASON:-ai_signal}"
757
+ fi
758
+
759
+ # 4. Convergence detected
760
+ if [[ -f "$PLAN_FILE" ]] && check_convergence "$STATE_FILE"; then
761
+ IS_COMPLETE=true
762
+ COMPLETION_REASON="${COMPLETION_REASON:-convergence}"
763
+ fi
764
+
765
+ # Handle synthesis pass
766
+ if [[ "$IS_COMPLETE" == "true" ]]; then
767
+ if [[ "$SYNTHESIS_DONE" == "true" ]]; then
768
+ # Research complete AND synthesis done -- allow stop
769
+ rm -f "$MARKER_FILE"
770
+
771
+ # Update state.json status
772
+ local final_status
773
+ case "$COMPLETION_REASON" in
774
+ max_iterations) final_status="stopped" ;;
775
+ convergence|confidence_target|ai_signal) final_status="complete" ;;
776
+ *) final_status="stopped" ;;
777
+ esac
778
+ jq --arg status "$final_status" --arg reason "$COMPLETION_REASON" \
779
+ '.status = $status | .stop_reason = $reason' \
780
+ "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
781
+
782
+ # Allow exit
783
+ exit 0
784
+ fi
785
+
786
+ # Complete but no synthesis yet -- trigger synthesis pass
787
+ update_marker "$NEXT_ITERATION" "synthesis-pass" "true"
788
+ update_state_json "$NEXT_ITERATION" "synthesis-pass"
789
+
790
+ # Build synthesis prompt
791
+ SYNTHESIS_PROMPT=$(build_synthesis_prompt "$COMPLETION_REASON")
792
+
793
+ SYSTEM_MSG="Oracle iteration $NEXT_ITERATION (SYNTHESIS PASS) -- Final report generation"
794
+
795
+ jq -n \
796
+ --arg prompt "$SYNTHESIS_PROMPT" \
797
+ --arg msg "$SYSTEM_MSG" \
798
+ '{
799
+ "decision": "block",
800
+ "reason": $prompt,
801
+ "systemMessage": $msg
802
+ }'
803
+
804
+ exit 0
805
+ fi
806
+
807
+ # ---------------------------------------------------------------------------
808
+ # NOT COMPLETE -- continue research loop
809
+ # ---------------------------------------------------------------------------
810
+
811
+ # Determine next phase from plan.json metrics
812
+ NEXT_PHASE=$(determine_next_phase "$STATE_FILE" "$PLAN_FILE")
813
+
814
+ # Check diminishing returns -- may force phase advancement
815
+ if [[ -f "$PLAN_FILE" ]]; then
816
+ DR_RESULT=$(detect_diminishing_returns "$STATE_FILE")
817
+ case "$DR_RESULT" in
818
+ strategy_change)
819
+ # Force synthesize phase
820
+ NEXT_PHASE="synthesize"
821
+ ;;
822
+ synthesize_now)
823
+ # Force early synthesis completion
824
+ NEXT_PHASE="verify"
825
+ ;;
826
+ esac
827
+ fi
828
+
829
+ # Update convergence metrics (reads plan.json, writes state.json)
830
+ if [[ -f "$PLAN_FILE" ]]; then
831
+ update_convergence_metrics "$STATE_FILE" "$PLAN_FILE" "$NEXT_ITERATION" "$NEXT_PHASE"
832
+ compute_trust_scores "$PLAN_FILE"
833
+ fi
834
+
835
+ # Update state files
836
+ update_marker "$NEXT_ITERATION" "$NEXT_PHASE" "$SYNTHESIS_DONE"
837
+ update_state_json "$NEXT_ITERATION" "$NEXT_PHASE"
838
+
839
+ # Generate research-plan.md summary
840
+ if [[ -f "$PLAN_FILE" ]]; then
841
+ {
842
+ echo "# Research Plan"
843
+ echo ""
844
+ local_topic=$(jq -r '.topic // "unknown"' "$STATE_FILE" 2>/dev/null || echo "unknown")
845
+ local_conf=$(jq '.overall_confidence // 0' "$STATE_FILE" 2>/dev/null || echo "0")
846
+ echo "**Topic:** $local_topic"
847
+ echo "**Status:** active | **Iteration:** $NEXT_ITERATION of $MAX_ITERATIONS"
848
+ echo "**Overall Confidence:** ${local_conf}%"
849
+ echo ""
850
+ echo "## Questions"
851
+ echo "| # | Question | Status | Confidence |"
852
+ echo "|---|----------|--------|------------|"
853
+ jq -r '.questions[] | "| \(.id) | \(.text) | \(.status) | \(.confidence)% |"' "$PLAN_FILE" 2>/dev/null || true
854
+ echo ""
855
+ echo "## Next Steps"
856
+ local_next
857
+ local_next=$(jq -r '[.questions[] | select(.status != "answered")] | sort_by(.confidence) | first | .text // "All questions answered"' "$PLAN_FILE" 2>/dev/null || echo "Continue research")
858
+ echo "Next investigation: $local_next"
859
+ echo ""
860
+ echo "---"
861
+ echo "*Generated from plan.json -- do not edit directly*"
862
+ } > "$ORACLE_DIR/research-plan.md"
863
+ fi
864
+
865
+ # Build the full iteration prompt
866
+ FULL_PROMPT=""
867
+
868
+ # Phase directive
869
+ FULL_PROMPT+=$(phase_directive_for "$NEXT_PHASE")
870
+
871
+ # Strategy modifier
872
+ FULL_PROMPT+=$(strategy_modifier "$STATE_FILE")
873
+
874
+ # Steering signals
875
+ STEERING=$(read_steering_signals "$AETHER_ROOT")
876
+ if [[ -n "$STEERING" ]]; then
877
+ FULL_PROMPT+="$STEERING"
878
+ fi
879
+
880
+ # Base oracle.md prompt
881
+ if [[ -f "$ORACLE_MD" ]]; then
882
+ FULL_PROMPT+=$(cat "$ORACLE_MD")
883
+ fi
884
+
885
+ SYSTEM_MSG="Oracle iteration $NEXT_ITERATION ($NEXT_PHASE phase) | Confidence: ${OVERALL_CONFIDENCE}% / ${TARGET}% | Iterations remaining: $((MAX_ITERATIONS - NEXT_ITERATION))"
886
+
887
+ jq -n \
888
+ --arg prompt "$FULL_PROMPT" \
889
+ --arg msg "$SYSTEM_MSG" \
890
+ '{
891
+ "decision": "block",
892
+ "reason": $prompt,
893
+ "systemMessage": $msg
894
+ }'
895
+
896
+ exit 0