aether-colony 3.1.5 → 3.1.16

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 (133) hide show
  1. package/.claude/commands/ant/archaeology.md +12 -0
  2. package/.claude/commands/ant/build.md +382 -319
  3. package/.claude/commands/ant/chaos.md +23 -1
  4. package/.claude/commands/ant/colonize.md +147 -87
  5. package/.claude/commands/ant/continue.md +213 -23
  6. package/.claude/commands/ant/council.md +22 -0
  7. package/.claude/commands/ant/dream.md +18 -0
  8. package/.claude/commands/ant/entomb.md +178 -6
  9. package/.claude/commands/ant/init.md +87 -13
  10. package/.claude/commands/ant/lay-eggs.md +45 -5
  11. package/.claude/commands/ant/oracle.md +82 -9
  12. package/.claude/commands/ant/organize.md +2 -2
  13. package/.claude/commands/ant/pause-colony.md +86 -28
  14. package/.claude/commands/ant/phase.md +26 -0
  15. package/.claude/commands/ant/plan.md +204 -111
  16. package/.claude/commands/ant/resume-colony.md +23 -1
  17. package/.claude/commands/ant/resume.md +159 -0
  18. package/.claude/commands/ant/seal.md +177 -3
  19. package/.claude/commands/ant/swarm.md +78 -97
  20. package/.claude/commands/ant/verify-castes.md +7 -7
  21. package/.claude/commands/ant/watch.md +17 -0
  22. package/.opencode/agents/aether-ambassador.md +97 -0
  23. package/.opencode/agents/aether-archaeologist.md +91 -0
  24. package/.opencode/agents/aether-architect.md +66 -0
  25. package/.opencode/agents/aether-auditor.md +111 -0
  26. package/.opencode/agents/aether-builder.md +28 -10
  27. package/.opencode/agents/aether-chaos.md +98 -0
  28. package/.opencode/agents/aether-chronicler.md +80 -0
  29. package/.opencode/agents/aether-gatekeeper.md +107 -0
  30. package/.opencode/agents/aether-guardian.md +107 -0
  31. package/.opencode/agents/aether-includer.md +108 -0
  32. package/.opencode/agents/aether-keeper.md +106 -0
  33. package/.opencode/agents/aether-measurer.md +119 -0
  34. package/.opencode/agents/aether-probe.md +91 -0
  35. package/.opencode/agents/aether-queen.md +72 -19
  36. package/.opencode/agents/aether-route-setter.md +85 -0
  37. package/.opencode/agents/aether-sage.md +98 -0
  38. package/.opencode/agents/aether-scout.md +33 -15
  39. package/.opencode/agents/aether-surveyor-disciplines.md +334 -0
  40. package/.opencode/agents/aether-surveyor-nest.md +272 -0
  41. package/.opencode/agents/aether-surveyor-pathogens.md +209 -0
  42. package/.opencode/agents/aether-surveyor-provisions.md +277 -0
  43. package/.opencode/agents/aether-tracker.md +91 -0
  44. package/.opencode/agents/aether-watcher.md +30 -12
  45. package/.opencode/agents/aether-weaver.md +87 -0
  46. package/.opencode/agents/workers.md +1034 -0
  47. package/.opencode/commands/ant/archaeology.md +44 -26
  48. package/.opencode/commands/ant/build.md +326 -294
  49. package/.opencode/commands/ant/chaos.md +32 -4
  50. package/.opencode/commands/ant/colonize.md +119 -93
  51. package/.opencode/commands/ant/continue.md +98 -10
  52. package/.opencode/commands/ant/council.md +28 -0
  53. package/.opencode/commands/ant/dream.md +24 -0
  54. package/.opencode/commands/ant/entomb.md +73 -1
  55. package/.opencode/commands/ant/feedback.md +8 -2
  56. package/.opencode/commands/ant/flag.md +9 -3
  57. package/.opencode/commands/ant/flags.md +8 -2
  58. package/.opencode/commands/ant/focus.md +8 -2
  59. package/.opencode/commands/ant/help.md +12 -0
  60. package/.opencode/commands/ant/init.md +49 -4
  61. package/.opencode/commands/ant/lay-eggs.md +30 -2
  62. package/.opencode/commands/ant/oracle.md +39 -7
  63. package/.opencode/commands/ant/organize.md +8 -2
  64. package/.opencode/commands/ant/pause-colony.md +54 -1
  65. package/.opencode/commands/ant/phase.md +36 -4
  66. package/.opencode/commands/ant/plan.md +224 -116
  67. package/.opencode/commands/ant/redirect.md +8 -2
  68. package/.opencode/commands/ant/resume-colony.md +51 -26
  69. package/.opencode/commands/ant/seal.md +76 -0
  70. package/.opencode/commands/ant/status.md +50 -20
  71. package/.opencode/commands/ant/swarm.md +108 -104
  72. package/.opencode/commands/ant/tunnels.md +107 -2
  73. package/CHANGELOG.md +16 -0
  74. package/README.md +199 -86
  75. package/bin/cli.js +142 -25
  76. package/bin/generate-commands.sh +100 -16
  77. package/bin/lib/caste-colors.js +5 -5
  78. package/bin/lib/errors.js +16 -0
  79. package/bin/lib/file-lock.js +279 -44
  80. package/bin/lib/state-sync.js +206 -23
  81. package/bin/lib/update-transaction.js +206 -24
  82. package/bin/sync-to-runtime.sh +138 -0
  83. package/package.json +2 -2
  84. package/runtime/CONTEXT.md +160 -0
  85. package/runtime/aether-utils.sh +1421 -55
  86. package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +1343 -0
  87. package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +2642 -0
  88. package/runtime/docs/PHEROMONE-INJECTION.md +240 -0
  89. package/runtime/docs/PHEROMONE-INTEGRATION.md +192 -0
  90. package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +426 -0
  91. package/runtime/docs/README.md +94 -0
  92. package/runtime/docs/VISUAL-OUTPUT-SPEC.md +219 -0
  93. package/runtime/docs/biological-reference.md +272 -0
  94. package/runtime/docs/codebase-review.md +399 -0
  95. package/runtime/docs/command-sync.md +164 -0
  96. package/runtime/docs/implementation-learnings.md +89 -0
  97. package/runtime/docs/known-issues.md +217 -0
  98. package/runtime/docs/namespace.md +148 -0
  99. package/runtime/docs/planning-discipline.md +159 -0
  100. package/runtime/exchange/pheromone-xml.sh +574 -0
  101. package/runtime/exchange/registry-xml.sh +269 -0
  102. package/runtime/exchange/wisdom-xml.sh +312 -0
  103. package/runtime/lib/queen-utils.sh +729 -0
  104. package/runtime/model-profiles.yaml +100 -0
  105. package/runtime/recover.sh +136 -0
  106. package/runtime/schemas/aether-types.xsd +255 -0
  107. package/runtime/schemas/colony-registry.xsd +309 -0
  108. package/runtime/schemas/pheromone.xsd +163 -0
  109. package/runtime/schemas/prompt.xsd +416 -0
  110. package/runtime/schemas/queen-wisdom.xsd +325 -0
  111. package/runtime/schemas/worker-priming.xsd +276 -0
  112. package/runtime/templates/QUEEN.md.template +79 -0
  113. package/runtime/utils/atomic-write.sh +5 -5
  114. package/runtime/utils/chamber-utils.sh +6 -3
  115. package/runtime/utils/error-handler.sh +200 -0
  116. package/runtime/utils/queen-to-md.xsl +395 -0
  117. package/runtime/utils/spawn-tree.sh +428 -0
  118. package/runtime/utils/spawn-with-model.sh +56 -0
  119. package/runtime/utils/state-loader.sh +215 -0
  120. package/runtime/utils/swarm-display.sh +5 -5
  121. package/runtime/utils/watch-spawn-tree.sh +90 -22
  122. package/runtime/utils/xml-compose.sh +247 -0
  123. package/runtime/utils/xml-core.sh +186 -0
  124. package/runtime/utils/xml-utils.sh +2196 -0
  125. package/runtime/verification-loop.md +1 -1
  126. package/runtime/workers-new-castes.md +516 -0
  127. package/runtime/workers.md +18 -6
  128. package/.aether/visualizations/anthill-stages/brood-stable.txt +0 -26
  129. package/.aether/visualizations/anthill-stages/crowned-anthill.txt +0 -30
  130. package/.aether/visualizations/anthill-stages/first-mound.txt +0 -18
  131. package/.aether/visualizations/anthill-stages/open-chambers.txt +0 -24
  132. package/.aether/visualizations/anthill-stages/sealed-chambers.txt +0 -28
  133. package/.aether/visualizations/anthill-stages/ventilated-nest.txt +0 -27
@@ -0,0 +1,729 @@
1
+ #!/bin/bash
2
+ # Queen Utilities — QUEEN.md management for Aether colonies
3
+ #
4
+ # Usage: source this file or run functions directly
5
+ # All functions output JSON for composability
6
+
7
+ set -euo pipefail
8
+
9
+ # Configuration
10
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ AETHER_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd 2>/dev/null || echo "$SCRIPT_DIR")"
12
+ RUNTIME_DIR="$AETHER_ROOT/runtime"
13
+ GLOBAL_QUEEN="${HOME}/.aether/QUEEN.md"
14
+
15
+ # Ensure directories exist
16
+ mkdir -p "${HOME}/.aether" 2>/dev/null || true
17
+
18
+ # --- JSON helpers ---
19
+ json_ok() { printf '{"ok":true,"result":%s}\n' "$1"; }
20
+ json_err() { printf '{"ok":false,"error":"%s"}\n' "$1" >&2; return 1; }
21
+ json_warn() { printf '{"ok":true,"warning":"%s"}\n' "$1"; }
22
+
23
+ # --- Template path ---
24
+ get_template_path() {
25
+ echo "$RUNTIME_DIR/templates/QUEEN.md.template"
26
+ }
27
+
28
+ # --- queen_init_global ---
29
+ # Initialize global QUEEN.md at ~/.aether/QUEEN.md
30
+ # Usage: queen_init_global
31
+ # Returns: JSON with path and status
32
+ queen_init_global() {
33
+ local template_path
34
+ template_path=$(get_template_path)
35
+
36
+ if [[ ! -f "$template_path" ]]; then
37
+ json_err "Template not found: $template_path"
38
+ return 1
39
+ fi
40
+
41
+ # If already exists, don't overwrite
42
+ if [[ -f "$GLOBAL_QUEEN" ]]; then
43
+ local version
44
+ version=$(queen_get_version "$GLOBAL_QUEEN" 2>/dev/null || echo "unknown")
45
+ json_ok "{\"initialized\":false,\"path\":\"$GLOBAL_QUEEN\",\"version\":\"$version\",\"reason\":\"already_exists\"}"
46
+ return 0
47
+ fi
48
+
49
+ # Create from template with current timestamp
50
+ local timestamp
51
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
52
+
53
+ # Replace {TIMESTAMP} placeholders
54
+ sed -e "s/{TIMESTAMP}/$timestamp/g" "$template_path" > "$GLOBAL_QUEEN"
55
+
56
+ json_ok "{\"initialized\":true,\"path\":\"$GLOBAL_QUEEN\",\"version\":\"1.0.0\"}"
57
+ }
58
+
59
+ # --- queen_init_local ---
60
+ # Initialize local QUEEN.md at repo/.aether/QUEEN.md
61
+ # Usage: queen_init_local [repo_path]
62
+ # Returns: JSON with path and status
63
+ queen_init_local() {
64
+ local repo_path="${1:-$(pwd)}"
65
+ local local_queen="$repo_path/.aether/QUEEN.md"
66
+ local template_path
67
+ template_path=$(get_template_path)
68
+
69
+ if [[ ! -f "$template_path" ]]; then
70
+ json_err "Template not found: $template_path"
71
+ return 1
72
+ fi
73
+
74
+ # Create .aether directory if needed
75
+ mkdir -p "$repo_path/.aether" 2>/dev/null || true
76
+
77
+ # If already exists, don't overwrite
78
+ if [[ -f "$local_queen" ]]; then
79
+ local version
80
+ version=$(queen_get_version "$local_queen" 2>/dev/null || echo "unknown")
81
+ json_ok "{\"initialized\":false,\"path\":\"$local_queen\",\"version\":\"$version\",\"reason\":\"already_exists\"}"
82
+ return 0
83
+ fi
84
+
85
+ # Create from template with current timestamp
86
+ local timestamp
87
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
88
+
89
+ # Replace {TIMESTAMP} placeholders
90
+ sed -e "s/{TIMESTAMP}/$timestamp/g" "$template_path" > "$local_queen"
91
+
92
+ json_ok "{\"initialized\":true,\"path\":\"$local_queen\",\"version\":\"1.0.0\"}"
93
+ }
94
+
95
+ # --- queen_parse_metadata ---
96
+ # Extract JSON metadata block from QUEEN.md
97
+ # Usage: queen_parse_metadata [queen_md_path]
98
+ # Returns: JSON metadata object
99
+ queen_parse_metadata() {
100
+ local queen_path="${1:-$GLOBAL_QUEEN}"
101
+
102
+ if [[ ! -f "$queen_path" ]]; then
103
+ json_err "QUEEN.md not found: $queen_path"
104
+ return 1
105
+ fi
106
+
107
+ # Extract content between <!-- METADATA and -->
108
+ local metadata
109
+ metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_path" | grep -v '<!--' | grep -v '-->')
110
+
111
+ if [[ -z "$metadata" ]]; then
112
+ json_err "No metadata block found in $queen_path"
113
+ return 1
114
+ fi
115
+
116
+ # Validate JSON
117
+ if ! echo "$metadata" | jq '.' >/dev/null 2>&1; then
118
+ json_err "Invalid JSON in metadata block"
119
+ return 1
120
+ fi
121
+
122
+ echo "$metadata"
123
+ }
124
+
125
+ # --- queen_get_version ---
126
+ # Get the semantic version from QUEEN.md metadata
127
+ # Usage: queen_get_version [queen_md_path]
128
+ # Returns: version string
129
+ queen_get_version() {
130
+ local queen_path="${1:-$GLOBAL_QUEEN}"
131
+
132
+ if [[ ! -f "$queen_path" ]]; then
133
+ json_err "QUEEN.md not found: $queen_path"
134
+ return 1
135
+ fi
136
+
137
+ local metadata
138
+ metadata=$(queen_parse_metadata "$queen_path" 2>/dev/null) || {
139
+ echo "0.0.0"
140
+ return 0
141
+ }
142
+
143
+ echo "$metadata" | jq -r '.version // "0.0.0"'
144
+ }
145
+
146
+ # --- queen_increment_version ---
147
+ # Increment the patch version in QUEEN.md
148
+ # Usage: queen_increment_version [queen_md_path] [component: patch|minor|major]
149
+ # Returns: JSON with old and new version
150
+ queen_increment_version() {
151
+ local queen_path="${1:-$GLOBAL_QUEEN}"
152
+ local component="${2:-patch}"
153
+
154
+ if [[ ! -f "$queen_path" ]]; then
155
+ json_err "QUEEN.md not found: $queen_path"
156
+ return 1
157
+ fi
158
+
159
+ local old_version
160
+ old_version=$(queen_get_version "$queen_path")
161
+
162
+ # Parse version components
163
+ local major minor patch
164
+ major=$(echo "$old_version" | cut -d. -f1)
165
+ minor=$(echo "$old_version" | cut -d. -f2)
166
+ patch=$(echo "$old_version" | cut -d. -f3)
167
+
168
+ # Increment appropriate component
169
+ case "$component" in
170
+ major)
171
+ major=$((major + 1))
172
+ minor=0
173
+ patch=0
174
+ ;;
175
+ minor)
176
+ minor=$((minor + 1))
177
+ patch=0
178
+ ;;
179
+ patch|*)
180
+ patch=$((patch + 1))
181
+ ;;
182
+ esac
183
+
184
+ local new_version="${major}.${minor}.${patch}"
185
+
186
+ # Update version in metadata block
187
+ local temp_file
188
+ temp_file=$(mktemp)
189
+
190
+ # Use jq to update the JSON inside the markdown
191
+ local metadata new_metadata
192
+ metadata=$(queen_parse_metadata "$queen_path")
193
+ new_metadata=$(echo "$metadata" | jq --arg v "$new_version" --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" '
194
+ .version = $v | .last_evolved = $ts
195
+ ')
196
+
197
+ # Replace metadata block in file
198
+ awk -v new_metadata="$new_metadata" '
199
+ /<!-- METADATA/ { print; print new_metadata; in_metadata=1; next }
200
+ /-->/ && in_metadata { print; in_metadata=0; next }
201
+ in_metadata { next }
202
+ { print }
203
+ ' "$queen_path" > "$temp_file"
204
+
205
+ mv "$temp_file" "$queen_path"
206
+
207
+ # Update header line too
208
+ sed -i.bak "s/^> Wisdom version: .*/> Wisdom version: $new_version/" "$queen_path" && rm -f "$queen_path.bak"
209
+
210
+ json_ok "{\"old_version\":\"$old_version\",\"new_version\":\"$new_version\",\"component\":\"$component\"}"
211
+ }
212
+
213
+ # --- queen_merge_global_local ---
214
+ # Merge global and local QUEEN.md content
215
+ # Usage: queen_merge_global_local [repo_path]
216
+ # Returns: JSON with merged content summary
217
+ queen_merge_global_local() {
218
+ local repo_path="${1:-$(pwd)}"
219
+ local local_queen="$repo_path/.aether/QUEEN.md"
220
+
221
+ # Ensure global exists
222
+ if [[ ! -f "$GLOBAL_QUEEN" ]]; then
223
+ queen_init_global >/dev/null 2>&1 || true
224
+ fi
225
+
226
+ # If no local, just use global
227
+ if [[ ! -f "$local_queen" ]]; then
228
+ json_ok "{\"source\":\"global_only\",\"global_path\":\"$GLOBAL_QUEEN\",\"has_local\":false}"
229
+ return 0
230
+ fi
231
+
232
+ # Both exist - count entries
233
+ local global_stats local_stats
234
+ global_stats=$(queen_parse_metadata "$GLOBAL_QUEEN" | jq '{
235
+ philosophies: .stats.total_philosophies,
236
+ patterns: .stats.total_patterns,
237
+ redirects: .stats.total_redirects
238
+ }' 2>/dev/null || echo '{}')
239
+
240
+ local_stats=$(queen_parse_metadata "$local_queen" | jq '{
241
+ philosophies: .stats.total_philosophies,
242
+ patterns: .stats.total_patterns,
243
+ redirects: .stats.total_redirects
244
+ }' 2>/dev/null || echo '{}')
245
+
246
+ json_ok "{
247
+ \"source\":\"merged\",
248
+ \"global_path\":\"$GLOBAL_QUEEN\",
249
+ \"local_path\":\"$local_queen\",
250
+ \"has_local\":true,
251
+ \"global_stats\":$global_stats,
252
+ \"local_stats\":$local_stats
253
+ }"
254
+ }
255
+
256
+ # --- queen_ensure_both ---
257
+ # Ensure both global and local QUEEN.md exist
258
+ # Usage: queen_ensure_both [repo_path]
259
+ # Returns: JSON with paths
260
+ queen_ensure_both() {
261
+ local repo_path="${1:-$(pwd)}"
262
+
263
+ local global_result local_result
264
+ global_result=$(queen_init_global)
265
+ local_result=$(queen_init_local "$repo_path")
266
+
267
+ json_ok "{
268
+ \"global\":$global_result,
269
+ \"local\":$local_result
270
+ }"
271
+ }
272
+
273
+ # --- queen_get_stats ---
274
+ # Get statistics from QUEEN.md
275
+ # Usage: queen_get_stats [queen_md_path]
276
+ # Returns: JSON with counts
277
+ queen_get_stats() {
278
+ local queen_path="${1:-$GLOBAL_QUEEN}"
279
+
280
+ if [[ ! -f "$queen_path" ]]; then
281
+ json_err "QUEEN.md not found: $queen_path"
282
+ return 1
283
+ fi
284
+
285
+ local metadata
286
+ metadata=$(queen_parse_metadata "$queen_path" 2>/dev/null) || {
287
+ json_ok '{"total_philosophies":0,"total_patterns":0,"total_redirects":0,"total_stack_entries":0,"total_decrees":0}'
288
+ return 0
289
+ }
290
+
291
+ echo "$metadata" | jq '.stats // {
292
+ "total_philosophies": 0,
293
+ "total_patterns": 0,
294
+ "total_redirects": 0,
295
+ "total_stack_entries": 0,
296
+ "total_decrees": 0
297
+ }'
298
+ }
299
+
300
+ # --- queen_promote_instincts ---
301
+ # Promote validated instincts from COLONY_STATE.json to QUEEN.md patterns
302
+ # Usage: queen_promote_instincts <queen_md_path> <colony_state_path>
303
+ # Returns: JSON with count of promoted instincts
304
+ queen_promote_instincts() {
305
+ local queen_md_path="$1"
306
+ local colony_state_path="$2"
307
+
308
+ # Validate inputs
309
+ if [[ -z "$queen_md_path" || -z "$colony_state_path" ]]; then
310
+ json_err "Usage: queen_promote_instincts <queen_md_path> <colony_state_path>"
311
+ return 1
312
+ fi
313
+
314
+ if [[ ! -f "$queen_md_path" ]]; then
315
+ json_err "QUEEN.md not found: $queen_md_path"
316
+ return 1
317
+ fi
318
+
319
+ if [[ ! -f "$colony_state_path" ]]; then
320
+ json_err "COLONY_STATE.json not found: $colony_state_path"
321
+ return 1
322
+ fi
323
+
324
+ # Extract validated instincts with confidence >= 0.8
325
+ local instincts_json
326
+ instincts_json=$(jq -r '
327
+ .memory.instincts // [] |
328
+ map(select(.confidence >= 0.8 and .status == "validated")) |
329
+ map({id, trigger, action, confidence, domain, evidence})
330
+ ' "$colony_state_path" 2>/dev/null) || {
331
+ json_err "Failed to parse COLONY_STATE.json"
332
+ return 1
333
+ }
334
+
335
+ # Count instincts to promote
336
+ local instinct_count
337
+ instinct_count=$(echo "$instincts_json" | jq 'length')
338
+
339
+ if [[ "$instinct_count" -eq 0 ]]; then
340
+ json_ok '{"promoted":0,"duplicates_skipped":0,"queen_md_path":"'"$queen_md_path"'","reason":"no_eligible_instincts"}'
341
+ return 0
342
+ fi
343
+
344
+ # Get existing patterns from QUEEN.md to check for duplicates
345
+ # Extract trigger+action combinations already in patterns section
346
+ local existing_patterns
347
+ existing_patterns=$(sed -n '/## 🧭 Patterns/,/## ⚠️ Redirects/p' "$queen_md_path" | \
348
+ grep -E '^\*\*Trigger:\*\*' | \
349
+ sed 's/^\*\*Trigger:\*\* //' | \
350
+ tr '\n' '|')
351
+
352
+ local promoted=0
353
+ local duplicates_skipped=0
354
+ local timestamp
355
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
356
+
357
+ # Process each instinct
358
+ local i
359
+ for ((i=0; i<instinct_count; i++)); do
360
+ local instinct trigger action domain evidence
361
+ instinct=$(echo "$instincts_json" | jq -r ".[$i]")
362
+ trigger=$(echo "$instinct" | jq -r '.trigger')
363
+ action=$(echo "$instinct" | jq -r '.action')
364
+ domain=$(echo "$instinct" | jq -r '.domain // "general"')
365
+ evidence=$(echo "$instinct" | jq -r '.evidence | if type == "array" then join("; ") else . end')
366
+
367
+ # Check for duplicate (trigger+action combination)
368
+ local pattern_key="${trigger}${action}"
369
+ if echo "$existing_patterns" | grep -qF "$trigger" 2>/dev/null; then
370
+ # Additional check: verify action also matches
371
+ local existing_action
372
+ existing_action=$(sed -n '/## 🧭 Patterns/,/## ⚠️ Redirects/p' "$queen_md_path" | \
373
+ grep -A1 "\\*\\*Trigger:\\*\\* ${trigger}$" | \
374
+ grep "\\*\\*Action:\\*\\*" | \
375
+ sed 's/^.*\\*\\*Action:\\*\\* //' | head -1)
376
+ if [[ "$existing_action" == "$action" ]]; then
377
+ duplicates_skipped=$((duplicates_skipped + 1))
378
+ continue
379
+ fi
380
+ fi
381
+
382
+ # Format the pattern entry
383
+ local pattern_entry="
384
+ **Trigger:** ${trigger}
385
+ **Action:** ${action}
386
+ **Domain:** ${domain}
387
+ **Evidence:** ${evidence}
388
+ *Promoted: ${timestamp}*
389
+ "
390
+
391
+ # Insert before the "*No patterns recorded yet*" line or at end of patterns section
392
+ local temp_file
393
+ temp_file=$(mktemp)
394
+
395
+ if grep -q "\\*No patterns recorded yet\\*" "$queen_md_path"; then
396
+ # Replace the placeholder with the pattern
397
+ awk -v entry="$pattern_entry" '
398
+ /## 🧭 Patterns/ { in_patterns=1 }
399
+ in_patterns && /\*No patterns recorded yet\*/ {
400
+ print entry
401
+ in_patterns=0
402
+ next
403
+ }
404
+ { print }
405
+ ' "$queen_md_path" > "$temp_file"
406
+ else
407
+ # Insert before the next section header
408
+ awk -v entry="$pattern_entry" '
409
+ /## 🧭 Patterns/ { in_patterns=1 }
410
+ in_patterns && /^## / && !/## 🧭 Patterns/ {
411
+ print entry
412
+ in_patterns=0
413
+ }
414
+ { print }
415
+ ' "$queen_md_path" > "$temp_file"
416
+ fi
417
+
418
+ mv "$temp_file" "$queen_md_path"
419
+ promoted=$((promoted + 1))
420
+ done
421
+
422
+ # Update stats in metadata if any were promoted
423
+ if [[ "$promoted" -gt 0 ]]; then
424
+ local current_stats new_stats
425
+ current_stats=$(queen_get_stats "$queen_md_path")
426
+ local current_patterns
427
+ current_patterns=$(echo "$current_stats" | jq -r '.total_patterns // 0')
428
+ local new_pattern_count=$((current_patterns + promoted))
429
+
430
+ # Update the metadata block
431
+ local temp_file
432
+ temp_file=$(mktemp)
433
+ local metadata new_metadata
434
+ metadata=$(queen_parse_metadata "$queen_md_path")
435
+ new_metadata=$(echo "$metadata" | jq --argjson count "$new_pattern_count" --arg ts "$timestamp" '
436
+ .stats.total_patterns = $count | .last_evolved = $ts
437
+ ')
438
+
439
+ awk -v new_metadata="$new_metadata" '
440
+ /<!-- METADATA/ { print; print new_metadata; in_metadata=1; next }
441
+ /-->/ && in_metadata { print; in_metadata=0; next }
442
+ in_metadata { next }
443
+ { print }
444
+ ' "$queen_md_path" > "$temp_file"
445
+
446
+ mv "$temp_file" "$queen_md_path"
447
+ fi
448
+
449
+ json_ok "{
450
+ \"promoted\": $promoted,
451
+ \"duplicates_skipped\": $duplicates_skipped,
452
+ \"queen_md_path\": \"$queen_md_path\",
453
+ \"colony_state_path\": \"$colony_state_path\"
454
+ }"
455
+ }
456
+
457
+ # --- queen_promote_learnings ---
458
+ # Promote validated phase learnings from COLONY_STATE.json to QUEEN.md patterns
459
+ # Usage: queen_promote_learnings <queen_md_path> <colony_state_path>
460
+ # Returns: JSON with count of promoted learnings
461
+ queen_promote_learnings() {
462
+ local queen_md_path="$1"
463
+ local colony_state_path="$2"
464
+
465
+ # Validate inputs
466
+ if [[ -z "$queen_md_path" || -z "$colony_state_path" ]]; then
467
+ json_err "Usage: queen_promote_learnings <queen_md_path> <colony_state_path>"
468
+ return 1
469
+ fi
470
+
471
+ if [[ ! -f "$queen_md_path" ]]; then
472
+ json_err "QUEEN.md not found: $queen_md_path"
473
+ return 1
474
+ fi
475
+
476
+ if [[ ! -f "$colony_state_path" ]]; then
477
+ json_err "COLONY_STATE.json not found: $colony_state_path"
478
+ return 1
479
+ fi
480
+
481
+ # Extract validated and tested learnings from phase_learnings
482
+ # Learnings are nested under memory.phase_learnings[].learnings[]
483
+ local learnings_json
484
+ learnings_json=$(jq -r '
485
+ .memory.phase_learnings // [] |
486
+ map(.learnings // [] | map(select(.status == "validated" and .tested == true))) |
487
+ flatten |
488
+ map({claim, evidence, source})
489
+ ' "$colony_state_path" 2>/dev/null) || {
490
+ json_err "Failed to parse COLONY_STATE.json"
491
+ return 1
492
+ }
493
+
494
+ # Count learnings to promote
495
+ local learning_count
496
+ learning_count=$(echo "$learnings_json" | jq 'length')
497
+
498
+ if [[ "$learning_count" -eq 0 ]]; then
499
+ json_ok '{"promoted":0,"duplicates_skipped":0,"queen_md_path":"'"$queen_md_path"'","reason":"no_eligible_learnings"}'
500
+ return 0
501
+ fi
502
+
503
+ # Get existing patterns from QUEEN.md to check for duplicates
504
+ local existing_patterns
505
+ existing_patterns=$(sed -n '/## 🧭 Patterns/,/## ⚠️ Redirects/p' "$queen_md_path" | \
506
+ grep -E '^\*\*Pattern\*\*:|^- \*\*Pattern\*\*:' | \
507
+ sed 's/^\*\*Pattern\*\*: //; s/^- \*\*Pattern\*\*: //' | \
508
+ cut -d'(' -f1 | \
509
+ tr '\n' '|')
510
+
511
+ local promoted=0
512
+ local duplicates_skipped=0
513
+ local timestamp
514
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
515
+
516
+ # Process each learning
517
+ local i
518
+ for ((i=0; i<learning_count; i++)); do
519
+ local learning claim evidence source
520
+ learning=$(echo "$learnings_json" | jq -r ".[$i]")
521
+ claim=$(echo "$learning" | jq -r '.claim // empty')
522
+ evidence=$(echo "$learning" | jq -r '.evidence // "Validated in colony session"')
523
+ source=$(echo "$learning" | jq -r '.source // "unknown"')
524
+
525
+ if [[ -z "$claim" ]]; then
526
+ continue
527
+ fi
528
+
529
+ # Check for duplicate (claim already exists)
530
+ local claim_prefix
531
+ claim_prefix=$(echo "$claim" | cut -c1-50)
532
+ if echo "$existing_patterns" | grep -qF "$claim_prefix" 2>/dev/null; then
533
+ duplicates_skipped=$((duplicates_skipped + 1))
534
+ continue
535
+ fi
536
+
537
+ # Format the pattern entry with source attribution
538
+ local pattern_entry="
539
+ **Pattern**: ${claim} (validated in colony: ${source})
540
+ **Evidence**: ${evidence}
541
+ *Promoted: ${timestamp}*
542
+ "
543
+
544
+ # Insert into QUEEN.md patterns section
545
+ local temp_file
546
+ temp_file=$(mktemp)
547
+
548
+ if grep -q "\\*No patterns recorded yet\\*" "$queen_md_path"; then
549
+ # Replace the placeholder with the pattern
550
+ awk -v entry="$pattern_entry" '
551
+ /## 🧭 Patterns/ { in_patterns=1 }
552
+ in_patterns && /\*No patterns recorded yet\*/ {
553
+ print entry
554
+ in_patterns=0
555
+ next
556
+ }
557
+ { print }
558
+ ' "$queen_md_path" > "$temp_file"
559
+ else
560
+ # Insert before the next section header
561
+ awk -v entry="$pattern_entry" '
562
+ /## 🧭 Patterns/ { in_patterns=1 }
563
+ in_patterns && /^## / && !/## 🧭 Patterns/ {
564
+ print entry
565
+ in_patterns=0
566
+ }
567
+ { print }
568
+ ' "$queen_md_path" > "$temp_file"
569
+ fi
570
+
571
+ mv "$temp_file" "$queen_md_path"
572
+ promoted=$((promoted + 1))
573
+ done
574
+
575
+ # Update stats in metadata if any were promoted
576
+ if [[ "$promoted" -gt 0 ]]; then
577
+ local current_stats current_patterns new_pattern_count
578
+ current_stats=$(queen_get_stats "$queen_md_path")
579
+ current_patterns=$(echo "$current_stats" | jq -r '.total_patterns // 0')
580
+ new_pattern_count=$((current_patterns + promoted))
581
+
582
+ # Update the metadata block
583
+ local temp_file metadata new_metadata
584
+ temp_file=$(mktemp)
585
+ metadata=$(queen_parse_metadata "$queen_md_path")
586
+ new_metadata=$(echo "$metadata" | jq --argjson count "$new_pattern_count" --arg ts "$timestamp" '
587
+ .stats.total_patterns = $count | .last_evolved = $ts
588
+ ')
589
+
590
+ awk -v new_metadata="$new_metadata" '
591
+ /<!-- METADATA/ { print; print new_metadata; in_metadata=1; next }
592
+ /-->/ && in_metadata { print; in_metadata=0; next }
593
+ in_metadata { next }
594
+ { print }
595
+ ' "$queen_md_path" > "$temp_file"
596
+
597
+ mv "$temp_file" "$queen_md_path"
598
+ fi
599
+
600
+ json_ok "{
601
+ \"promoted\": $promoted,
602
+ \"duplicates_skipped\": $duplicates_skipped,
603
+ \"queen_md_path\": \"$queen_md_path\",
604
+ \"colony_state_path\": \"$colony_state_path\"
605
+ }"
606
+ }
607
+
608
+ # --- queen_promote_all ---
609
+ # Promote all validated pheromones (instincts + learnings) from COLONY_STATE.json to QUEEN.md
610
+ # Usage: queen_promote_all <queen_md_path> <colony_state_path>
611
+ # Returns: JSON with combined promotion results
612
+ queen_promote_all() {
613
+ local queen_md_path="$1"
614
+ local colony_state_path="$2"
615
+
616
+ # Validate inputs
617
+ if [[ -z "$queen_md_path" || -z "$colony_state_path" ]]; then
618
+ json_err "Usage: queen_promote_all <queen_md_path> <colony_state_path>"
619
+ return 1
620
+ fi
621
+
622
+ if [[ ! -f "$queen_md_path" ]]; then
623
+ json_err "QUEEN.md not found: $queen_md_path"
624
+ return 1
625
+ fi
626
+
627
+ if [[ ! -f "$colony_state_path" ]]; then
628
+ json_err "COLONY_STATE.json not found: $colony_state_path"
629
+ return 1
630
+ fi
631
+
632
+ local instincts_result learnings_result
633
+ local instincts_promoted=0
634
+ local learnings_promoted=0
635
+ local instincts_error=""
636
+ local learnings_error=""
637
+
638
+ # Promote instincts (with error handling)
639
+ if instincts_result=$(queen_promote_instincts "$queen_md_path" "$colony_state_path" 2>&1); then
640
+ instincts_promoted=$(echo "$instincts_result" | jq -r '.promoted // 0')
641
+ else
642
+ instincts_error=$(echo "$instincts_result" | jq -r '.error // "unknown error"' 2>/dev/null || echo "failed to promote instincts")
643
+ fi
644
+
645
+ # Promote learnings (with error handling - continues even if instincts failed)
646
+ if learnings_result=$(queen_promote_learnings "$queen_md_path" "$colony_state_path" 2>&1); then
647
+ learnings_promoted=$(echo "$learnings_result" | jq -r '.promoted // 0')
648
+ else
649
+ learnings_error=$(echo "$learnings_result" | jq -r '.error // "unknown error"' 2>/dev/null || echo "failed to promote learnings")
650
+ fi
651
+
652
+ local total=$((instincts_promoted + learnings_promoted))
653
+
654
+ # Build result JSON
655
+ local result_json
656
+ result_json=$(jq -n \
657
+ --argjson instincts "$instincts_promoted" \
658
+ --argjson learnings "$learnings_promoted" \
659
+ --argjson total "$total" \
660
+ --arg queen_md "$queen_md_path" \
661
+ --arg colony_state "$colony_state_path" \
662
+ --arg instincts_err "$instincts_error" \
663
+ --arg learnings_err "$learnings_error" \
664
+ '{
665
+ instincts_promoted: $instincts,
666
+ learnings_promoted: $learnings,
667
+ total: $total,
668
+ queen_md_path: $queen_md,
669
+ colony_state_path: $colony_state,
670
+ errors: {
671
+ instincts: (if $instincts_err == "" then null else $instincts_err end),
672
+ learnings: (if $learnings_err == "" then null else $learnings_err end)
673
+ }
674
+ }')
675
+
676
+ json_ok "$result_json"
677
+ }
678
+
679
+ # --- Main entry point for command-line usage ---
680
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
681
+ cmd="${1:-help}"
682
+ shift 2>/dev/null || true
683
+
684
+ case "$cmd" in
685
+ init-global)
686
+ queen_init_global
687
+ ;;
688
+ init-local)
689
+ queen_init_local "${1:-$(pwd)}"
690
+ ;;
691
+ ensure-both)
692
+ queen_ensure_both "${1:-$(pwd)}"
693
+ ;;
694
+ parse-metadata)
695
+ queen_parse_metadata "${1:-$GLOBAL_QUEEN}"
696
+ ;;
697
+ get-version)
698
+ result=$(queen_get_version "${1:-$GLOBAL_QUEEN}")
699
+ json_ok "\"$result\""
700
+ ;;
701
+ increment-version)
702
+ queen_increment_version "${1:-$GLOBAL_QUEEN}" "${2:-patch}"
703
+ ;;
704
+ merge)
705
+ queen_merge_global_local "${1:-$(pwd)}"
706
+ ;;
707
+ stats)
708
+ queen_get_stats "${1:-$GLOBAL_QUEEN}"
709
+ ;;
710
+ promote-instincts)
711
+ queen_promote_instincts "${1:-}" "${2:-}"
712
+ ;;
713
+ promote-learnings)
714
+ queen_promote_learnings "${1:-}" "${2:-}"
715
+ ;;
716
+ promote-all)
717
+ queen_promote_all "${1:-}" "${2:-}"
718
+ ;;
719
+ help|*)
720
+ cat <<'EOF'
721
+ {"ok":true,"commands":[
722
+ "init-global","init-local","ensure-both",
723
+ "parse-metadata","get-version","increment-version",
724
+ "merge","stats","promote-instincts","promote-learnings","promote-all"
725
+ ],"description":"Queen Utilities — QUEEN.md management for Aether colonies"}
726
+ EOF
727
+ ;;
728
+ esac
729
+ fi