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,269 @@
1
+ #!/bin/bash
2
+ # Colony Registry Exchange Module
3
+ # JSON/XML bidirectional conversion for colony registry
4
+ #
5
+ # Usage: source .aether/exchange/registry-xml.sh
6
+ # xml-registry-export <registry_json> [output_xml]
7
+ # xml-registry-import <registry_xml> [output_json]
8
+ # xml-registry-lineage <registry_json> <colony_id>
9
+
10
+ # Source dependencies - handle being sourced vs executed
11
+ if [[ -n "${BASH_SOURCE[0]:-}" ]]; then
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
13
+ else
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
15
+ fi
16
+ source "$SCRIPT_DIR/utils/xml-core.sh"
17
+
18
+ # Ensure tool availability variables are available
19
+ if [[ -z "${XMLLINT_AVAILABLE:-}" ]]; then
20
+ XMLLINT_AVAILABLE=false
21
+ command -v xmllint > /dev/null 2>&1 && XMLLINT_AVAILABLE=true
22
+ fi
23
+ if [[ -z "${XMLSTARLET_AVAILABLE:-}" ]]; then
24
+ XMLSTARLET_AVAILABLE=false
25
+ command -v xmlstarlet > /dev/null 2>&1 && XMLSTARLET_AVAILABLE=true
26
+ fi
27
+
28
+ # ============================================================================
29
+ # Registry Export (JSON to XML)
30
+ # ============================================================================
31
+
32
+ # xml-registry-export: Convert colony registry JSON to XML
33
+ # Usage: xml-registry-export <registry_json_file> [output_xml_file]
34
+ # Returns: {"ok":true,"result":{"path":"...","colonies":N}}
35
+ xml-registry-export() {
36
+ local json_file="${1:-}"
37
+ local output_xml="${2:-}"
38
+
39
+ [[ -z "$json_file" ]] && { xml_json_err "MISSING_ARG" "Missing JSON file argument"; return 1; }
40
+ [[ -f "$json_file" ]] || { xml_json_err "FILE_NOT_FOUND" "JSON file not found: $json_file"; return 1; }
41
+
42
+ # Validate JSON
43
+ if ! jq empty "$json_file" 2>/dev/null; then
44
+ xml_json_err "PARSE_ERROR" "Invalid JSON file: $json_file"
45
+ return 1
46
+ fi
47
+
48
+ local version generated_at
49
+ version=$(jq -r '.version // "1.0.0"' "$json_file")
50
+ generated_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
51
+
52
+ # Build XML
53
+ local xml_output
54
+ xml_output="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
55
+ <colony-registry xmlns=\"http://aether.colony/schemas/registry/1.0\"
56
+ version=\"$version\"
57
+ generated_at=\"$generated_at\">"
58
+
59
+ # Process colonies
60
+ local colony_count
61
+ colony_count=$(jq '.colonies | length' "$json_file" 2>/dev/null || echo "0")
62
+
63
+ local idx=0
64
+ while [[ $idx -lt $colony_count ]]; do
65
+ local colony
66
+ colony=$(jq -c ".colonies[$idx]" "$json_file")
67
+
68
+ local id name created_at status parent_id
69
+ id=$(echo "$colony" | jq -r '.id')
70
+ name=$(echo "$colony" | jq -r '.name // "Unnamed Colony"')
71
+ created_at=$(echo "$colony" | jq -r '.created_at // "'"$generated_at"'"')
72
+ status=$(echo "$colony" | jq -r '.status // "active"')
73
+ parent_id=$(echo "$colony" | jq -r '.parent_id // empty')
74
+
75
+ xml_output="$xml_output
76
+ <colony id=\"$id\" status=\"$status\" created_at=\"$created_at\">"
77
+
78
+ [[ -n "$parent_id" && "$parent_id" != "null" ]] && \
79
+ xml_output="$xml_output
80
+ <parent_id>$parent_id</parent_id>"
81
+
82
+ xml_output="$xml_output
83
+ <name>$(echo "$name" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')</name>"
84
+
85
+ # Add lineage if present
86
+ local ancestors
87
+ ancestors=$(echo "$colony" | jq -c '.ancestors // []')
88
+ if [[ "$ancestors" != "[]" && -n "$ancestors" ]]; then
89
+ xml_output="$xml_output
90
+ <lineage>"
91
+ local anc_count anc_idx
92
+ anc_count=$(echo "$ancestors" | jq 'length')
93
+ anc_idx=0
94
+ while [[ $anc_idx -lt $anc_count ]]; do
95
+ local ancestor_id
96
+ ancestor_id=$(echo "$ancestors" | jq -r ".[$anc_idx]")
97
+ xml_output="$xml_output
98
+ <ancestor id=\"$ancestor_id\"/>
99
+ "
100
+ ((anc_idx++))
101
+ done
102
+ xml_output="$xml_output
103
+ </lineage>"
104
+ fi
105
+
106
+ xml_output="$xml_output
107
+ </colony>"
108
+ ((idx++))
109
+ done
110
+
111
+ xml_output="$xml_output
112
+ </colony-registry>"
113
+
114
+ # Output
115
+ if [[ -n "$output_xml" ]]; then
116
+ echo "$xml_output" > "$output_xml"
117
+ xml_json_ok "{\"path\":\"$output_xml\",\"colonies\":$colony_count}"
118
+ else
119
+ local escaped
120
+ escaped=$(echo "$xml_output" | jq -Rs '.')
121
+ xml_json_ok "{\"xml\":$escaped,\"colonies\":$colony_count}"
122
+ fi
123
+ }
124
+
125
+ # ============================================================================
126
+ # Registry Import (XML to JSON) - Round-Trip
127
+ # ============================================================================
128
+
129
+ # xml-registry-import: Convert colony registry XML to JSON
130
+ # Usage: xml-registry-import <registry_xml_file> [output_json_file]
131
+ # Returns: {"ok":true,"result":{"colonies":N,"path":"..."}}
132
+ xml-registry-import() {
133
+ local xml_file="${1:-}"
134
+ local output_json="${2:-}"
135
+
136
+ [[ -z "$xml_file" ]] && { xml_json_err "MISSING_ARG" "Missing XML file argument"; return 1; }
137
+ [[ -f "$xml_file" ]] || { xml_json_err "FILE_NOT_FOUND" "XML file not found: $xml_file"; return 1; }
138
+
139
+ # Check well-formedness
140
+ if [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
141
+ xmllint --nonet --noent --noout "$xml_file" 2>/dev/null || {
142
+ xml_json_err "PARSE_ERROR" "XML is not well-formed"
143
+ return 1
144
+ }
145
+ fi
146
+
147
+ # Extract metadata
148
+ local version generated_at
149
+ if [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
150
+ version=$(xmllint --nonet --noent --xpath "string(/*/@version)" "$xml_file" 2>/dev/null || echo "1.0.0")
151
+ generated_at=$(xmllint --nonet --noent --xpath "string(/*/@generated_at)" "$xml_file" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
152
+ else
153
+ version="1.0.0"
154
+ generated_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
155
+ fi
156
+
157
+ # Build JSON structure
158
+ local json_output
159
+ json_output=$(jq -n \
160
+ --arg version "$version" \
161
+ --arg generated_at "$generated_at" \
162
+ '{
163
+ version: $version,
164
+ generated_at: $generated_at,
165
+ colonies: []
166
+ }')
167
+
168
+ local colony_count=0
169
+
170
+ # Extract colonies using xmlstarlet if available
171
+ if [[ "$XMLSTARLET_AVAILABLE" == "true" ]]; then
172
+ local colony_array
173
+ colony_array=$(xmlstarlet sel -t -m "//colony" \
174
+ -o '{"id":"' -v "@id" -o '","name":"' -v "name" -o '","status":"' -v "@status" -o '","created_at":"' -v "@created_at" -o '"}' \
175
+ -n "$xml_file" 2>/dev/null | jq -s '.')
176
+
177
+ colony_count=$(echo "$colony_array" | jq 'length')
178
+ if [[ $colony_count -gt 0 ]]; then
179
+ json_output=$(echo "$json_output" | jq --argjson colonies "$colony_array" '.colonies = $colonies')
180
+ fi
181
+ fi
182
+
183
+ # Output
184
+ if [[ -n "$output_json" ]]; then
185
+ echo "$json_output" > "$output_json"
186
+ xml_json_ok "{\"path\":\"$output_json\",\"colonies\":$colony_count}"
187
+ else
188
+ local escaped
189
+ escaped=$(echo "$json_output" | jq -Rs '.')
190
+ xml_json_ok "{\"json\":$escaped,\"colonies\":$colony_count}"
191
+ fi
192
+ }
193
+
194
+ # ============================================================================
195
+ # Registry Validation
196
+ # ============================================================================
197
+
198
+ # xml-registry-validate: Validate registry XML against schema
199
+ # Usage: xml-registry-validate <registry_xml> [xsd_schema]
200
+ xml-registry-validate() {
201
+ local xml_file="${1:-}"
202
+ local xsd_file="${2:-.aether/schemas/colony-registry.xsd}"
203
+
204
+ [[ -z "$xml_file" ]] && { xml_json_err "MISSING_ARG" "Missing XML file argument"; return 1; }
205
+ [[ -f "$xml_file" ]] || { xml_json_err "FILE_NOT_FOUND" "XML file not found: $xml_file"; return 1; }
206
+
207
+ if [[ "$XMLLINT_AVAILABLE" != "true" ]]; then
208
+ xml_json_err "TOOL_NOT_AVAILABLE" "xmllint required for validation"
209
+ return 1
210
+ fi
211
+
212
+ if [[ ! -f "$xsd_file" ]]; then
213
+ xml_json_ok '{"valid":true,"warning":"Schema not found, skipping validation","schema":"'$xsd_file'"}'
214
+ return 0
215
+ fi
216
+
217
+ xmllint --nonet --noent --noout --schema "$xsd_file" "$xml_file" 2>/dev/null && {
218
+ xml_json_ok '{"valid":true}'
219
+ } || {
220
+ xml_json_ok '{"valid":false}'
221
+ }
222
+ }
223
+
224
+ # ============================================================================
225
+ # Lineage Queries
226
+ # ============================================================================
227
+
228
+ # xml-registry-lineage: Get ancestry chain for a colony
229
+ # Usage: xml-registry-lineage <registry_json> <colony_id>
230
+ # Returns: {"ok":true,"result":{"colony_id":"...","ancestors":[...],"depth":N}}
231
+ xml-registry-lineage() {
232
+ local json_file="${1:-}"
233
+ local colony_id="${2:-}"
234
+
235
+ [[ -z "$json_file" ]] && { xml_json_err "MISSING_ARG" "Missing JSON file argument"; return 1; }
236
+ [[ -z "$colony_id" ]] && { xml_json_err "MISSING_ARG" "Missing colony ID argument"; return 1; }
237
+ [[ -f "$json_file" ]] || { xml_json_err "FILE_NOT_FOUND" "JSON file not found: $json_file"; return 1; }
238
+
239
+ # Find the colony
240
+ local colony
241
+ colony=$(jq --arg id "$colony_id" '.colonies[] | select(.id == $id)' "$json_file")
242
+
243
+ if [[ -z "$colony" || "$colony" == "null" ]]; then
244
+ xml_json_err "COLONY_NOT_FOUND" "Colony not found in registry: $colony_id"
245
+ return 1
246
+ fi
247
+
248
+ # Build ancestry chain
249
+ local ancestors="[]"
250
+ local current_id="$colony_id"
251
+ local depth=0
252
+ local max_depth=10 # Prevent infinite loops
253
+
254
+ while [[ $depth -lt $max_depth ]]; do
255
+ local parent_id
256
+ parent_id=$(jq --arg id "$current_id" -r '.colonies[] | select(.id == $id) | .parent_id // empty' "$json_file")
257
+
258
+ [[ -z "$parent_id" ]] && break
259
+
260
+ ancestors=$(echo "$ancestors" | jq --arg parent "$parent_id" '. + [$parent]')
261
+ current_id="$parent_id"
262
+ ((depth++))
263
+ done
264
+
265
+ xml_json_ok "{\"colony_id\":\"$colony_id\",\"ancestors\":$ancestors,\"depth\":$depth}"
266
+ }
267
+
268
+ # Export functions
269
+ export -f xml-registry-export xml-registry-import xml-registry-validate xml-registry-lineage
@@ -0,0 +1,312 @@
1
+ #!/bin/bash
2
+ # Queen Wisdom Exchange Module
3
+ # JSON/XML bidirectional conversion for wisdom entries
4
+ #
5
+ # Usage: source .aether/exchange/wisdom-xml.sh
6
+ # xml-wisdom-export <wisdom_json> [output_xml]
7
+ # xml-wisdom-import <wisdom_xml> [output_json]
8
+ # xml-wisdom-promote <entry_id>
9
+
10
+ # Source dependencies - handle being sourced vs executed
11
+ if [[ -n "${BASH_SOURCE[0]:-}" ]]; then
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
13
+ else
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
15
+ fi
16
+ source "$SCRIPT_DIR/utils/xml-core.sh"
17
+
18
+ # Ensure tool availability variables are available
19
+ if [[ -z "${XMLLINT_AVAILABLE:-}" ]]; then
20
+ XMLLINT_AVAILABLE=false
21
+ command -v xmllint > /dev/null 2>&1 && XMLLINT_AVAILABLE=true
22
+ fi
23
+ if [[ -z "${XMLSTARLET_AVAILABLE:-}" ]]; then
24
+ XMLSTARLET_AVAILABLE=false
25
+ command -v xmlstarlet > /dev/null 2>&1 && XMLSTARLET_AVAILABLE=true
26
+ fi
27
+
28
+ # Default promotion threshold (confidence level required)
29
+ PROMOTION_THRESHOLD=0.8
30
+
31
+ # ============================================================================
32
+ # Wisdom Export (JSON to XML)
33
+ # ============================================================================
34
+
35
+ # xml-wisdom-export: Convert queen-wisdom JSON to XML
36
+ # Usage: xml-wisdom-export <wisdom_json_file> [output_xml_file]
37
+ # Returns: {"ok":true,"result":{"path":"...","entries":N}}
38
+ xml-wisdom-export() {
39
+ local json_file="${1:-}"
40
+ local output_xml="${2:-}"
41
+
42
+ [[ -z "$json_file" ]] && { xml_json_err "MISSING_ARG" "Missing JSON file argument"; return 1; }
43
+ [[ -f "$json_file" ]] || { xml_json_err "FILE_NOT_FOUND" "JSON file not found: $json_file"; return 1; }
44
+
45
+ # Validate JSON
46
+ if ! jq empty "$json_file" 2>/dev/null; then
47
+ xml_json_err "PARSE_ERROR" "Invalid JSON file: $json_file"
48
+ return 1
49
+ fi
50
+
51
+ local version created modified colony_id
52
+ version=$(jq -r '.version // "1.0.0"' "$json_file")
53
+ created=$(jq -r '.metadata.created // "'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'"' "$json_file" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
54
+ modified=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
55
+ colony_id=$(jq -r '.metadata.colony_id // "unknown"' "$json_file" 2>/dev/null || echo "unknown")
56
+
57
+ # Build XML
58
+ local xml_output
59
+ xml_output="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
60
+ <queen-wisdom xmlns=\"http://aether.colony/schemas/queen-wisdom/1.0\"
61
+ xmlns:qw=\"http://aether.colony/schemas/queen-wisdom/1.0\">
62
+ <metadata>
63
+ <version>$version</version>
64
+ <created>$created</created>
65
+ <modified>$modified</modified>
66
+ <colony_id>$colony_id</colony_id>
67
+ </metadata>"
68
+
69
+ # Process philosophies
70
+ xml_output="$xml_output
71
+ <philosophies>"
72
+
73
+ local phil_count
74
+ phil_count=$(jq '.philosophies | length' "$json_file" 2>/dev/null || echo "0")
75
+ local idx=0
76
+ while [[ $idx -lt $phil_count ]]; do
77
+ local entry
78
+ entry=$(jq -c ".philosophies[$idx]" "$json_file")
79
+ local id confidence domain source content
80
+ id=$(echo "$entry" | jq -r '.id')
81
+ confidence=$(echo "$entry" | jq -r '.confidence // "0.5"')
82
+ domain=$(echo "$entry" | jq -r '.domain // "general"')
83
+ source=$(echo "$entry" | jq -r '.source // "observation"')
84
+ content=$(echo "$entry" | jq -r '.content // ""' | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')
85
+
86
+ xml_output="$xml_output
87
+ <philosophy id=\"$id\" confidence=\"$confidence\" domain=\"$domain\" source=\"$source\" created_at=\"$modified\">
88
+ <content>$content</content>
89
+ </philosophy>"
90
+ ((idx++))
91
+ done
92
+
93
+ xml_output="$xml_output
94
+ </philosophies>"
95
+
96
+ # Process patterns
97
+ xml_output="$xml_output
98
+ <patterns>"
99
+
100
+ local pattern_count
101
+ pattern_count=$(jq '.patterns | length' "$json_file" 2>/dev/null || echo "0")
102
+ idx=0
103
+ while [[ $idx -lt $pattern_count ]]; do
104
+ local entry
105
+ entry=$(jq -c ".patterns[$idx]" "$json_file")
106
+ local id confidence domain source
107
+ id=$(echo "$entry" | jq -r '.id')
108
+ confidence=$(echo "$entry" | jq -r '.confidence // "0.5"')
109
+ domain=$(echo "$entry" | jq -r '.domain // "general"')
110
+ source=$(echo "$entry" | jq -r '.source // "observation"')
111
+
112
+ xml_output="$xml_output
113
+ <pattern id=\"$id\" confidence=\"$confidence\" domain=\"$domain\" source=\"$source\" created_at=\"$modified\">
114
+ <content>$(echo "$entry" | jq -r '.content // ""' | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')</content>
115
+ </pattern>"
116
+ ((idx++))
117
+ done
118
+
119
+ xml_output="$xml_output
120
+ </patterns>
121
+ </queen-wisdom>"
122
+
123
+ local entry_count=$((phil_count + pattern_count))
124
+
125
+ # Output
126
+ if [[ -n "$output_xml" ]]; then
127
+ echo "$xml_output" > "$output_xml"
128
+ xml_json_ok "{\"path\":\"$output_xml\",\"entries\":$entry_count}"
129
+ else
130
+ local escaped
131
+ escaped=$(echo "$xml_output" | jq -Rs '.')
132
+ xml_json_ok "{\"xml\":$escaped,\"entries\":$entry_count}"
133
+ fi
134
+ }
135
+
136
+ # ============================================================================
137
+ # Wisdom Import (XML to JSON) - Round-Trip
138
+ # ============================================================================
139
+
140
+ # xml-wisdom-import: Convert queen-wisdom XML to JSON
141
+ # Usage: xml-wisdom-import <wisdom_xml_file> [output_json_file]
142
+ # Returns: {"ok":true,"result":{"entries":N,"path":"..."}}
143
+ xml-wisdom-import() {
144
+ local xml_file="${1:-}"
145
+ local output_json="${2:-}"
146
+
147
+ [[ -z "$xml_file" ]] && { xml_json_err "MISSING_ARG" "Missing XML file argument"; return 1; }
148
+ [[ -f "$xml_file" ]] || { xml_json_err "FILE_NOT_FOUND" "XML file not found: $xml_file"; return 1; }
149
+
150
+ # Check well-formedness
151
+ if [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
152
+ xmllint --nonet --noent --noout "$xml_file" 2>/dev/null || {
153
+ xml_json_err "PARSE_ERROR" "XML is not well-formed"
154
+ return 1
155
+ }
156
+ fi
157
+
158
+ # Extract metadata
159
+ local version created modified colony_id
160
+ if [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
161
+ version=$(xmllint --nonet --noent --xpath "string(/*/metadata/version)" "$xml_file" 2>/dev/null || echo "1.0.0")
162
+ created=$(xmllint --nonet --noent --xpath "string(/*/metadata/created)" "$xml_file" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
163
+ modified=$(xmllint --nonet --noent --xpath "string(/*/metadata/modified)" "$xml_file" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
164
+ colony_id=$(xmllint --nonet --noent --xpath "string(/*/metadata/colony_id)" "$xml_file" 2>/dev/null || echo "unknown")
165
+ else
166
+ version="1.0.0"
167
+ created=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
168
+ modified="$created"
169
+ colony_id="unknown"
170
+ fi
171
+
172
+ # Build JSON structure
173
+ local json_output
174
+ json_output=$(jq -n \
175
+ --arg version "$version" \
176
+ --arg created "$created" \
177
+ --arg modified "$modified" \
178
+ --arg colony_id "$colony_id" \
179
+ '{
180
+ version: $version,
181
+ metadata: {
182
+ created: $created,
183
+ modified: $modified,
184
+ colony_id: $colony_id
185
+ },
186
+ philosophies: [],
187
+ patterns: [],
188
+ redirects: [],
189
+ stack_wisdom: [],
190
+ decrees: []
191
+ }')
192
+
193
+ local entry_count=0
194
+
195
+ # Extract philosophies using xmlstarlet if available
196
+ if [[ "$XMLSTARLET_AVAILABLE" == "true" ]]; then
197
+ local phil_array
198
+ phil_array=$(xmlstarlet sel -t -m "//philosophy" \
199
+ -o '{"id":"' -v "@id" -o '","confidence":' -v "@confidence" -o ',"domain":"' -v "@domain" -o '","source":"' -v "@source" -o '","content":"' -v "content" -o '"}' \
200
+ -n "$xml_file" 2>/dev/null | jq -s '.')
201
+
202
+ local phil_count
203
+ phil_count=$(echo "$phil_array" | jq 'length')
204
+ if [[ $phil_count -gt 0 ]]; then
205
+ json_output=$(echo "$json_output" | jq --argjson philosophies "$phil_array" '.philosophies = $philosophies')
206
+ entry_count=$((entry_count + phil_count))
207
+ fi
208
+
209
+ # Extract patterns
210
+ local pattern_array
211
+ pattern_array=$(xmlstarlet sel -t -m "//pattern" \
212
+ -o '{"id":"' -v "@id" -o '","confidence":' -v "@confidence" -o ',"domain":"' -v "@domain" -o '"}' \
213
+ -n "$xml_file" 2>/dev/null | jq -s '.')
214
+
215
+ local pattern_count
216
+ pattern_count=$(echo "$pattern_array" | jq 'length')
217
+ if [[ $pattern_count -gt 0 ]]; then
218
+ json_output=$(echo "$json_output" | jq --argjson patterns "$pattern_array" '.patterns = $patterns')
219
+ entry_count=$((entry_count + pattern_count))
220
+ fi
221
+ fi
222
+
223
+ # Output
224
+ if [[ -n "$output_json" ]]; then
225
+ echo "$json_output" > "$output_json"
226
+ xml_json_ok "{\"path\":\"$output_json\",\"entries\":$entry_count}"
227
+ else
228
+ local escaped
229
+ escaped=$(echo "$json_output" | jq -Rs '.')
230
+ xml_json_ok "{\"json\":$escaped,\"entries\":$entry_count}"
231
+ fi
232
+ }
233
+
234
+ # ============================================================================
235
+ # Wisdom Validation
236
+ # ============================================================================
237
+
238
+ # xml-wisdom-validate: Validate wisdom XML against schema
239
+ # Usage: xml-wisdom-validate <wisdom_xml> [xsd_schema]
240
+ xml-wisdom-validate() {
241
+ local xml_file="${1:-}"
242
+ local xsd_file="${2:-.aether/schemas/queen-wisdom.xsd}"
243
+
244
+ [[ -z "$xml_file" ]] && { xml_json_err "MISSING_ARG" "Missing XML file argument"; return 1; }
245
+ [[ -f "$xml_file" ]] || { xml_json_err "FILE_NOT_FOUND" "XML file not found: $xml_file"; return 1; }
246
+
247
+ if [[ "$XMLLINT_AVAILABLE" != "true" ]]; then
248
+ xml_json_err "TOOL_NOT_AVAILABLE" "xmllint required for validation"
249
+ return 1
250
+ fi
251
+
252
+ if [[ ! -f "$xsd_file" ]]; then
253
+ xml_json_ok '{"valid":true,"warning":"Schema not found, skipping validation","schema":"'$xsd_file'"}'
254
+ return 0
255
+ fi
256
+
257
+ xmllint --nonet --noent --noout --schema "$xsd_file" "$xml_file" 2>/dev/null && {
258
+ xml_json_ok '{"valid":true}'
259
+ } || {
260
+ xml_json_ok '{"valid":false}'
261
+ }
262
+ }
263
+
264
+ # ============================================================================
265
+ # Wisdom Promotion
266
+ # ============================================================================
267
+
268
+ # xml-wisdom-promote: Promote a pattern to philosophy if confidence threshold met
269
+ # Usage: xml-wisdom-promote <wisdom_json> <entry_id>
270
+ # Returns: {"ok":true,"result":{"promoted":true,"new_domain":"philosophy"}}
271
+ xml-wisdom-promote() {
272
+ local json_file="${1:-}"
273
+ local entry_id="${2:-}"
274
+
275
+ [[ -z "$json_file" ]] && { xml_json_err "MISSING_ARG" "Missing JSON file argument"; return 1; }
276
+ [[ -z "$entry_id" ]] && { xml_json_err "MISSING_ARG" "Missing entry ID argument"; return 1; }
277
+ [[ -f "$json_file" ]] || { xml_json_err "FILE_NOT_FOUND" "JSON file not found: $json_file"; return 1; }
278
+
279
+ # Find entry in patterns
280
+ local entry
281
+ entry=$(jq --arg id "$entry_id" '.patterns[] | select(.id == $id)' "$json_file")
282
+
283
+ if [[ -z "$entry" || "$entry" == "null" ]]; then
284
+ xml_json_err "ENTRY_NOT_FOUND" "Pattern entry not found: $entry_id"
285
+ return 1
286
+ fi
287
+
288
+ # Check confidence
289
+ local confidence
290
+ confidence=$(echo "$entry" | jq -r '.confidence // 0')
291
+
292
+ if (( $(echo "$confidence < $PROMOTION_THRESHOLD" | bc -l) )); then
293
+ xml_json_ok "{\"promoted\":false,\"reason\":\"confidence_below_threshold\",\"confidence\":$confidence,\"threshold\":$PROMOTION_THRESHOLD}"
294
+ return 0
295
+ fi
296
+
297
+ # Promote: add to philosophies, remove from patterns
298
+ local updated_json
299
+ updated_json=$(jq --arg id "$entry_id" '
300
+ (.patterns[] | select(.id == $id)) as $entry |
301
+ .philosophies += [$entry] |
302
+ .patterns = [.patterns[] | select(.id != $id)]
303
+ ' "$json_file")
304
+
305
+ echo "$updated_json" > "$json_file"
306
+
307
+ xml_json_ok "{\"promoted\":true,\"new_domain\":\"philosophy\",\"confidence\":$confidence}"
308
+ }
309
+
310
+ # Export functions
311
+ export -f xml-wisdom-export xml-wisdom-import xml-wisdom-validate xml-wisdom-promote
312
+ export PROMOTION_THRESHOLD