aether-colony 1.1.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 (207) hide show
  1. package/.aether/CONTEXT.md +160 -0
  2. package/.aether/QUEEN.md +84 -0
  3. package/.aether/aether-utils.sh +7749 -0
  4. package/.aether/docs/QUEEN-SYSTEM.md +211 -0
  5. package/.aether/docs/README.md +68 -0
  6. package/.aether/docs/caste-system.md +48 -0
  7. package/.aether/docs/disciplines/DISCIPLINES.md +93 -0
  8. package/.aether/docs/disciplines/coding-standards.md +197 -0
  9. package/.aether/docs/disciplines/debugging.md +207 -0
  10. package/.aether/docs/disciplines/learning.md +254 -0
  11. package/.aether/docs/disciplines/tdd.md +257 -0
  12. package/.aether/docs/disciplines/verification-loop.md +167 -0
  13. package/.aether/docs/disciplines/verification.md +116 -0
  14. package/.aether/docs/error-codes.md +268 -0
  15. package/.aether/docs/known-issues.md +233 -0
  16. package/.aether/docs/pheromones.md +205 -0
  17. package/.aether/docs/queen-commands.md +97 -0
  18. package/.aether/exchange/colony-registry.xml +11 -0
  19. package/.aether/exchange/pheromone-xml.sh +575 -0
  20. package/.aether/exchange/pheromones.xml +87 -0
  21. package/.aether/exchange/queen-wisdom.xml +14 -0
  22. package/.aether/exchange/registry-xml.sh +273 -0
  23. package/.aether/exchange/wisdom-xml.sh +319 -0
  24. package/.aether/midden/approach-changes.md +5 -0
  25. package/.aether/midden/build-failures.md +5 -0
  26. package/.aether/midden/test-failures.md +5 -0
  27. package/.aether/model-profiles.yaml +100 -0
  28. package/.aether/rules/aether-colony.md +134 -0
  29. package/.aether/schemas/aether-types.xsd +255 -0
  30. package/.aether/schemas/colony-registry.xsd +309 -0
  31. package/.aether/schemas/example-prompt-builder.xml +234 -0
  32. package/.aether/schemas/pheromone.xsd +163 -0
  33. package/.aether/schemas/prompt.xsd +416 -0
  34. package/.aether/schemas/queen-wisdom.xsd +325 -0
  35. package/.aether/schemas/worker-priming.xsd +276 -0
  36. package/.aether/templates/QUEEN.md.template +79 -0
  37. package/.aether/templates/colony-state-reset.jq.template +22 -0
  38. package/.aether/templates/colony-state.template.json +35 -0
  39. package/.aether/templates/constraints.template.json +9 -0
  40. package/.aether/templates/crowned-anthill.template.md +36 -0
  41. package/.aether/templates/handoff-build-error.template.md +30 -0
  42. package/.aether/templates/handoff-build-success.template.md +39 -0
  43. package/.aether/templates/handoff.template.md +40 -0
  44. package/.aether/templates/learning-observations.template.json +6 -0
  45. package/.aether/templates/midden.template.json +7 -0
  46. package/.aether/templates/pheromones.template.json +6 -0
  47. package/.aether/templates/session.template.json +9 -0
  48. package/.aether/utils/atomic-write.sh +219 -0
  49. package/.aether/utils/chamber-compare.sh +193 -0
  50. package/.aether/utils/chamber-utils.sh +297 -0
  51. package/.aether/utils/colorize-log.sh +132 -0
  52. package/.aether/utils/error-handler.sh +212 -0
  53. package/.aether/utils/file-lock.sh +158 -0
  54. package/.aether/utils/queen-to-md.xsl +395 -0
  55. package/.aether/utils/semantic-cli.sh +413 -0
  56. package/.aether/utils/spawn-tree.sh +428 -0
  57. package/.aether/utils/spawn-with-model.sh +56 -0
  58. package/.aether/utils/state-loader.sh +215 -0
  59. package/.aether/utils/swarm-display.sh +268 -0
  60. package/.aether/utils/watch-spawn-tree.sh +253 -0
  61. package/.aether/utils/xml-compose.sh +253 -0
  62. package/.aether/utils/xml-convert.sh +273 -0
  63. package/.aether/utils/xml-core.sh +186 -0
  64. package/.aether/utils/xml-query.sh +201 -0
  65. package/.aether/utils/xml-utils.sh +110 -0
  66. package/.aether/workers.md +765 -0
  67. package/.claude/agents/ant/aether-ambassador.md +264 -0
  68. package/.claude/agents/ant/aether-archaeologist.md +322 -0
  69. package/.claude/agents/ant/aether-auditor.md +266 -0
  70. package/.claude/agents/ant/aether-builder.md +187 -0
  71. package/.claude/agents/ant/aether-chaos.md +268 -0
  72. package/.claude/agents/ant/aether-chronicler.md +304 -0
  73. package/.claude/agents/ant/aether-gatekeeper.md +325 -0
  74. package/.claude/agents/ant/aether-includer.md +373 -0
  75. package/.claude/agents/ant/aether-keeper.md +271 -0
  76. package/.claude/agents/ant/aether-measurer.md +317 -0
  77. package/.claude/agents/ant/aether-probe.md +210 -0
  78. package/.claude/agents/ant/aether-queen.md +325 -0
  79. package/.claude/agents/ant/aether-route-setter.md +173 -0
  80. package/.claude/agents/ant/aether-sage.md +353 -0
  81. package/.claude/agents/ant/aether-scout.md +142 -0
  82. package/.claude/agents/ant/aether-surveyor-disciplines.md +416 -0
  83. package/.claude/agents/ant/aether-surveyor-nest.md +354 -0
  84. package/.claude/agents/ant/aether-surveyor-pathogens.md +288 -0
  85. package/.claude/agents/ant/aether-surveyor-provisions.md +359 -0
  86. package/.claude/agents/ant/aether-tracker.md +265 -0
  87. package/.claude/agents/ant/aether-watcher.md +244 -0
  88. package/.claude/agents/ant/aether-weaver.md +247 -0
  89. package/.claude/commands/ant/archaeology.md +341 -0
  90. package/.claude/commands/ant/build.md +1160 -0
  91. package/.claude/commands/ant/chaos.md +349 -0
  92. package/.claude/commands/ant/colonize.md +270 -0
  93. package/.claude/commands/ant/continue.md +1070 -0
  94. package/.claude/commands/ant/council.md +309 -0
  95. package/.claude/commands/ant/dream.md +265 -0
  96. package/.claude/commands/ant/entomb.md +487 -0
  97. package/.claude/commands/ant/feedback.md +78 -0
  98. package/.claude/commands/ant/flag.md +139 -0
  99. package/.claude/commands/ant/flags.md +155 -0
  100. package/.claude/commands/ant/focus.md +58 -0
  101. package/.claude/commands/ant/help.md +122 -0
  102. package/.claude/commands/ant/history.md +137 -0
  103. package/.claude/commands/ant/init.md +409 -0
  104. package/.claude/commands/ant/interpret.md +267 -0
  105. package/.claude/commands/ant/lay-eggs.md +201 -0
  106. package/.claude/commands/ant/maturity.md +102 -0
  107. package/.claude/commands/ant/memory-details.md +77 -0
  108. package/.claude/commands/ant/migrate-state.md +165 -0
  109. package/.claude/commands/ant/oracle.md +387 -0
  110. package/.claude/commands/ant/organize.md +227 -0
  111. package/.claude/commands/ant/pause-colony.md +247 -0
  112. package/.claude/commands/ant/phase.md +126 -0
  113. package/.claude/commands/ant/plan.md +544 -0
  114. package/.claude/commands/ant/redirect.md +58 -0
  115. package/.claude/commands/ant/resume-colony.md +182 -0
  116. package/.claude/commands/ant/resume.md +363 -0
  117. package/.claude/commands/ant/seal.md +306 -0
  118. package/.claude/commands/ant/status.md +272 -0
  119. package/.claude/commands/ant/swarm.md +361 -0
  120. package/.claude/commands/ant/tunnels.md +425 -0
  121. package/.claude/commands/ant/update.md +209 -0
  122. package/.claude/commands/ant/verify-castes.md +95 -0
  123. package/.claude/commands/ant/watch.md +238 -0
  124. package/.opencode/agents/aether-ambassador.md +140 -0
  125. package/.opencode/agents/aether-archaeologist.md +108 -0
  126. package/.opencode/agents/aether-auditor.md +144 -0
  127. package/.opencode/agents/aether-builder.md +184 -0
  128. package/.opencode/agents/aether-chaos.md +115 -0
  129. package/.opencode/agents/aether-chronicler.md +122 -0
  130. package/.opencode/agents/aether-gatekeeper.md +116 -0
  131. package/.opencode/agents/aether-includer.md +117 -0
  132. package/.opencode/agents/aether-keeper.md +177 -0
  133. package/.opencode/agents/aether-measurer.md +128 -0
  134. package/.opencode/agents/aether-probe.md +133 -0
  135. package/.opencode/agents/aether-queen.md +286 -0
  136. package/.opencode/agents/aether-route-setter.md +130 -0
  137. package/.opencode/agents/aether-sage.md +106 -0
  138. package/.opencode/agents/aether-scout.md +101 -0
  139. package/.opencode/agents/aether-surveyor-disciplines.md +386 -0
  140. package/.opencode/agents/aether-surveyor-nest.md +324 -0
  141. package/.opencode/agents/aether-surveyor-pathogens.md +259 -0
  142. package/.opencode/agents/aether-surveyor-provisions.md +329 -0
  143. package/.opencode/agents/aether-tracker.md +137 -0
  144. package/.opencode/agents/aether-watcher.md +174 -0
  145. package/.opencode/agents/aether-weaver.md +130 -0
  146. package/.opencode/commands/ant/archaeology.md +338 -0
  147. package/.opencode/commands/ant/build.md +1200 -0
  148. package/.opencode/commands/ant/chaos.md +346 -0
  149. package/.opencode/commands/ant/colonize.md +202 -0
  150. package/.opencode/commands/ant/continue.md +938 -0
  151. package/.opencode/commands/ant/council.md +305 -0
  152. package/.opencode/commands/ant/dream.md +262 -0
  153. package/.opencode/commands/ant/entomb.md +367 -0
  154. package/.opencode/commands/ant/feedback.md +80 -0
  155. package/.opencode/commands/ant/flag.md +137 -0
  156. package/.opencode/commands/ant/flags.md +153 -0
  157. package/.opencode/commands/ant/focus.md +56 -0
  158. package/.opencode/commands/ant/help.md +124 -0
  159. package/.opencode/commands/ant/history.md +127 -0
  160. package/.opencode/commands/ant/init.md +337 -0
  161. package/.opencode/commands/ant/interpret.md +256 -0
  162. package/.opencode/commands/ant/lay-eggs.md +141 -0
  163. package/.opencode/commands/ant/maturity.md +92 -0
  164. package/.opencode/commands/ant/memory-details.md +77 -0
  165. package/.opencode/commands/ant/migrate-state.md +153 -0
  166. package/.opencode/commands/ant/oracle.md +338 -0
  167. package/.opencode/commands/ant/organize.md +224 -0
  168. package/.opencode/commands/ant/pause-colony.md +220 -0
  169. package/.opencode/commands/ant/phase.md +123 -0
  170. package/.opencode/commands/ant/plan.md +531 -0
  171. package/.opencode/commands/ant/redirect.md +67 -0
  172. package/.opencode/commands/ant/resume-colony.md +178 -0
  173. package/.opencode/commands/ant/resume.md +363 -0
  174. package/.opencode/commands/ant/seal.md +247 -0
  175. package/.opencode/commands/ant/status.md +272 -0
  176. package/.opencode/commands/ant/swarm.md +357 -0
  177. package/.opencode/commands/ant/tunnels.md +406 -0
  178. package/.opencode/commands/ant/update.md +191 -0
  179. package/.opencode/commands/ant/verify-castes.md +85 -0
  180. package/.opencode/commands/ant/watch.md +220 -0
  181. package/.opencode/opencode.json +3 -0
  182. package/CHANGELOG.md +325 -0
  183. package/DISCLAIMER.md +74 -0
  184. package/LICENSE +21 -0
  185. package/README.md +258 -0
  186. package/bin/cli.js +2436 -0
  187. package/bin/generate-commands.sh +291 -0
  188. package/bin/lib/caste-colors.js +57 -0
  189. package/bin/lib/colors.js +76 -0
  190. package/bin/lib/errors.js +255 -0
  191. package/bin/lib/event-types.js +190 -0
  192. package/bin/lib/file-lock.js +695 -0
  193. package/bin/lib/init.js +454 -0
  194. package/bin/lib/logger.js +242 -0
  195. package/bin/lib/model-profiles.js +445 -0
  196. package/bin/lib/model-verify.js +288 -0
  197. package/bin/lib/nestmate-loader.js +130 -0
  198. package/bin/lib/proxy-health.js +253 -0
  199. package/bin/lib/spawn-logger.js +266 -0
  200. package/bin/lib/state-guard.js +602 -0
  201. package/bin/lib/state-sync.js +516 -0
  202. package/bin/lib/telemetry.js +441 -0
  203. package/bin/lib/update-transaction.js +1454 -0
  204. package/bin/npx-install.js +178 -0
  205. package/bin/sync-to-runtime.sh +6 -0
  206. package/bin/validate-package.sh +88 -0
  207. package/package.json +70 -0
@@ -0,0 +1,253 @@
1
+ #!/bin/bash
2
+ # XML Composition Functions for Worker Priming
3
+ # Part of xml-utils.sh - XInclude-based modular configuration composition
4
+ #
5
+ # Usage: source .aether/utils/xml-compose.sh
6
+ # xml-compose <input_xml> [output_xml]
7
+ # xml-compose-worker-priming <priming_xml> [output_xml]
8
+ # xml-list-includes <xml_file>
9
+ #
10
+ # These functions enable declarative composition of worker configurations
11
+ # using XInclude directives to merge queen-wisdom, active-trails, and stack-profiles.
12
+
13
+ set -euo pipefail
14
+
15
+ # Note: This file should be sourced AFTER xml-utils.sh or xml-core.sh
16
+ # It relies on xml_json_ok, xml_json_err, and XMLLINT_AVAILABLE variables
17
+ # Source xml-core.sh for JSON helpers and tool detection if not already loaded
18
+ if ! type xml_json_ok &>/dev/null; then
19
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
+ source "$SCRIPT_DIR/xml-core.sh"
21
+ fi
22
+
23
+ # ============================================================================
24
+ # Path Validation (Security)
25
+ # ============================================================================
26
+
27
+ # xml-validate-include-path: Validate XInclude path for traversal attacks
28
+ # Usage: xml-validate-include-path <include_path> <base_dir>
29
+ # Returns: absolute path on success, exits with error on failure
30
+ xml-validate-include-path() {
31
+ local include_path="$1"
32
+ local base_dir="$2"
33
+
34
+ # Resolve base directory to absolute path
35
+ local allowed_dir
36
+ allowed_dir=$(cd "$base_dir" 2>/dev/null && pwd) || {
37
+ xml_json_err "INVALID_BASE_DIR" \
38
+ "Base directory does not exist" \
39
+ "dir=$base_dir"
40
+ return 1
41
+ }
42
+
43
+ # First check: reject paths with traversal sequences
44
+ if [[ "$include_path" =~ \.\.[\/] ]] || [[ "$include_path" =~ [\/]\.\. ]]; then
45
+ xml_json_err "PATH_TRAVERSAL_DETECTED" \
46
+ "Path contains traversal sequences" \
47
+ "path=$include_path"
48
+ return 1
49
+ fi
50
+
51
+ # Build resolved path
52
+ local resolved_path
53
+ if [[ "$include_path" == /* ]]; then
54
+ # Absolute path - must start with allowed_dir
55
+ if [[ ! "$include_path" =~ ^"$allowed_dir" ]]; then
56
+ xml_json_err "PATH_TRAVERSAL_BLOCKED" \
57
+ "Absolute path outside allowed directory" \
58
+ "path=$include_path, allowed=$allowed_dir"
59
+ return 1
60
+ fi
61
+ resolved_path="$include_path"
62
+ else
63
+ # Relative path - resolve against base_dir
64
+ resolved_path="$allowed_dir/$include_path"
65
+ fi
66
+
67
+ # Normalize path (remove . and .. components manually for portability)
68
+ local normalized_path="$resolved_path"
69
+
70
+ # Verify final path is within allowed directory
71
+ if [[ ! "$normalized_path" =~ ^"$allowed_dir" ]]; then
72
+ xml_json_err "PATH_TRAVERSAL_BLOCKED" \
73
+ "Resolved path outside allowed directory" \
74
+ "path=$normalized_path, allowed=$allowed_dir"
75
+ return 1
76
+ fi
77
+
78
+ echo "$normalized_path"
79
+ }
80
+
81
+ # ============================================================================
82
+ # XInclude Composition Functions
83
+ # ============================================================================
84
+
85
+ # xml-compose: Resolve XInclude directives in worker priming documents
86
+ # Usage: xml-compose <input_xml> [output_xml]
87
+ # Returns: {"ok":true,"result":{"composed":true,"output":"...","sources_resolved":N}}
88
+ xml-compose() {
89
+ local input_xml="${1:-}"
90
+ local output_xml="${2:-}"
91
+
92
+ [[ -z "$input_xml" ]] && { xml_json_err "Missing input XML file argument"; return 1; }
93
+ [[ -f "$input_xml" ]] || { xml_json_err "Input XML file not found: $input_xml"; return 1; }
94
+
95
+ # Check well-formedness first
96
+ local well_formed_result
97
+ well_formed_result=$(xml-well-formed "$input_xml" 2>/dev/null)
98
+ if ! echo "$well_formed_result" | jq -e '.result.well_formed' >/dev/null 2>&1; then
99
+ xml_json_err "Input XML is not well-formed"
100
+ return 1
101
+ fi
102
+
103
+ # Use xmllint for XInclude processing if available (with XXE protection)
104
+ if [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
105
+ local composed
106
+ composed=$(xmllint --nonet --noent --xinclude --format "$input_xml" 2>/dev/null) || {
107
+ xml_json_err "XInclude composition failed - check that included files exist"
108
+ return 1
109
+ }
110
+
111
+ # Determine output destination
112
+ if [[ -n "$output_xml" ]]; then
113
+ echo "$composed" > "$output_xml"
114
+ local escaped_output
115
+ escaped_output=$(echo "$output_xml" | jq -Rs '.[:-1]')
116
+ xml_json_ok "{\"composed\":true,\"output\":$escaped_output,\"sources_resolved\":\"auto\"}"
117
+ else
118
+ # Output to stdout wrapped in JSON
119
+ local escaped_composed
120
+ escaped_composed=$(echo "$composed" | jq -Rs '.')
121
+ xml_json_ok "{\"composed\":true,\"xml\":$escaped_composed,\"sources_resolved\":\"auto\"}"
122
+ fi
123
+ return 0
124
+ fi
125
+
126
+ # No xmllint available - error explicitly (manual fallback removed for security)
127
+ xml_json_err "XMLLINT_REQUIRED" \
128
+ "xmllint is required for secure XInclude processing" \
129
+ "install_hint='brew install libxml2'"
130
+ return 1
131
+ }
132
+
133
+ # xml-list-includes: List all XInclude references in a document
134
+ # Usage: xml-list-includes <xml_file>
135
+ # Returns: {"ok":true,"result":{"includes":[{"href":"...","parse":"..."},...]}}
136
+ xml-list-includes() {
137
+ local xml_file="${1:-}"
138
+
139
+ [[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
140
+ [[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
141
+
142
+ local includes_json="[]"
143
+
144
+ if [[ "$XMLSTARLET_AVAILABLE" == "true" ]]; then
145
+ # Use xmlstarlet for proper namespace-aware extraction
146
+ includes_json=$(xmlstarlet sel -N xi="http://www.w3.org/2001/XInclude" \
147
+ -t -m "//xi:include" \
148
+ -o '{"href":"' -v "@href" -o '","parse":"' -v "@parse" -o '","xpointer":"' -v "@xpointer" -o '"}' \
149
+ -n "$xml_file" 2>/dev/null | jq -s '.' || echo "[]")
150
+ elif [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
151
+ # Fallback: grep for xi:include (less reliable but portable)
152
+ local base_dir
153
+ base_dir=$(dirname "$xml_file")
154
+
155
+ includes_json=$(grep -oE 'xi:include[^>]*href="[^"]+"' "$xml_file" 2>/dev/null | while read -r match; do
156
+ local href
157
+ href=$(echo "$match" | grep -oE 'href="[^"]+"' | cut -d'"' -f2)
158
+ local parse
159
+ parse=$(echo "$match" | grep -oE 'parse="[^"]+"' | cut -d'"' -f2 || echo "xml")
160
+ echo "{\"href\":\"$href\",\"parse\":\"$parse\",\"resolved\":\"$base_dir/$href\"}"
161
+ done | jq -s '.' || echo "[]")
162
+ else
163
+ xml_json_err "No XML tool available. Install xmlstarlet or libxml2."
164
+ return 1
165
+ fi
166
+
167
+ local count
168
+ count=$(echo "$includes_json" | jq 'length')
169
+ xml_json_ok "{\"includes\":$includes_json,\"count\":$count}"
170
+ }
171
+
172
+ # xml-compose-worker-priming: Specialized composition for worker priming documents
173
+ # Usage: xml-compose-worker-priming <priming_xml> [output_xml]
174
+ # Returns: {"ok":true,"result":{"composed":true,"worker_id":"...","caste":"...","sources":{...}}}
175
+ xml-compose-worker-priming() {
176
+ local priming_xml="${1:-}"
177
+ local output_xml="${2:-}"
178
+
179
+ [[ -z "$priming_xml" ]] && { xml_json_err "Missing priming XML file argument"; return 1; }
180
+ [[ -f "$priming_xml" ]] || { xml_json_err "Priming XML file not found: $priming_xml"; return 1; }
181
+
182
+ # Validate against schema if available
183
+ local schema_file=".aether/schemas/worker-priming.xsd"
184
+ if [[ -f "$schema_file" ]] && [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
185
+ local validation
186
+ validation=$(xml-validate "$priming_xml" "$schema_file" 2>/dev/null)
187
+ if ! echo "$validation" | jq -e '.result.valid' >/dev/null 2>&1; then
188
+ xml_json_err "Worker priming XML failed schema validation"
189
+ return 1
190
+ fi
191
+ fi
192
+
193
+ # Extract worker identity before composition
194
+ local worker_id worker_caste
195
+ if [[ "$XMLSTARLET_AVAILABLE" == "true" ]]; then
196
+ worker_id=$(xmlstarlet sel -t -v "//*[local-name()='worker-identity']/@id" "$priming_xml" 2>/dev/null || echo "unknown")
197
+ worker_caste=$(xmlstarlet sel -t -v "//*[local-name()='worker-identity']/*[local-name()='caste']" "$priming_xml" 2>/dev/null || echo "unknown")
198
+ else
199
+ # Fallback: sed extraction (portable, no grep -P)
200
+ worker_id=$(sed -n 's/.*worker-identity[^>]*id="\([^"]*\)".*/\1/p' "$priming_xml" | head -1 || echo "unknown")
201
+ worker_caste=$(sed -n 's/.*<caste>\([^<]*\)<\/caste>.*/\1/p' "$priming_xml" | head -1 || echo "unknown")
202
+ fi
203
+
204
+ # Compose the document
205
+ local compose_result
206
+ if [[ -n "$output_xml" ]]; then
207
+ compose_result=$(xml-compose "$priming_xml" "$output_xml" 2>/dev/null)
208
+ else
209
+ compose_result=$(xml-compose "$priming_xml" 2>/dev/null)
210
+ fi
211
+
212
+ if ! echo "$compose_result" | jq -e '.ok' >/dev/null 2>&1; then
213
+ xml_json_err "Composition failed: $(echo "$compose_result" | jq -r '.error // "unknown"')"
214
+ return 1
215
+ fi
216
+
217
+ # Count sources from different sections
218
+ local queen_wisdom_count active_trails_count stack_profiles_count
219
+ if [[ "$XMLSTARLET_AVAILABLE" == "true" ]] && [[ -n "$output_xml" ]]; then
220
+ queen_wisdom_count=$(xmlstarlet sel -t -v "count(//*[local-name()='queen-wisdom']/*[local-name()='wisdom-source'])" "$output_xml" 2>/dev/null || echo "0")
221
+ active_trails_count=$(xmlstarlet sel -t -v "count(//*[local-name()='active-trails']/*[local-name()='trail-source'])" "$output_xml" 2>/dev/null || echo "0")
222
+ stack_profiles_count=$(xmlstarlet sel -t -v "count(//*[local-name()='stack-profiles']/*[local-name()='profile-source'])" "$output_xml" 2>/dev/null || echo "0")
223
+ else
224
+ queen_wisdom_count="unknown"
225
+ active_trails_count="unknown"
226
+ stack_profiles_count="unknown"
227
+ fi
228
+
229
+ # Build result
230
+ local result_json
231
+ result_json=$(jq -n \
232
+ --arg worker_id "$worker_id" \
233
+ --arg caste "$worker_caste" \
234
+ --arg queen_wisdom "$queen_wisdom_count" \
235
+ --arg active_trails "$active_trails_count" \
236
+ --arg stack_profiles "$stack_profiles_count" \
237
+ '{
238
+ composed: true,
239
+ worker_id: $worker_id,
240
+ caste: $caste,
241
+ sources: {
242
+ queen_wisdom: $queen_wisdom,
243
+ active_trails: $active_trails,
244
+ stack_profiles: $stack_profiles
245
+ }
246
+ }')
247
+
248
+ xml_json_ok "$result_json"
249
+ }
250
+
251
+ # Export functions
252
+ export -f xml-compose xml-list-includes xml-compose-worker-priming
253
+ export -f xml-validate-include-path
@@ -0,0 +1,273 @@
1
+ #!/bin/bash
2
+ # XML Conversion Utilities
3
+ # Bidirectional JSON/XML conversion and document merging
4
+ #
5
+ # Usage: source .aether/utils/xml-convert.sh
6
+ # xml-to-json <xml_file> [--pretty]
7
+ # json-to-xml <json_file> [root_element]
8
+ # xml-merge <output_file> <main_xml_file>
9
+ # xml-convert-detect-format <file>
10
+
11
+ set -euo pipefail
12
+
13
+ # Source xml-core.sh for JSON helpers and tool detection
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+ source "$SCRIPT_DIR/xml-core.sh"
16
+
17
+ # Additional tool detection for conversion
18
+ XML2JSON_AVAILABLE=false
19
+ if command -v xml2json >/dev/null 2>&1; then
20
+ XML2JSON_AVAILABLE=true
21
+ fi
22
+
23
+ # ============================================================================
24
+ # Format Detection
25
+ # ============================================================================
26
+
27
+ # xml-convert-detect-format: Detect if file is XML or JSON
28
+ # Usage: xml-convert-detect-format <file>
29
+ # Returns: {"ok":true,"result":{"format":"xml|json|unknown","confidence":"high|medium|low"}}
30
+ xml-convert-detect-format() {
31
+ local file="${1:-}"
32
+
33
+ [[ -z "$file" ]] && { xml_json_err "MISSING_ARG" "Missing file argument"; return 1; }
34
+ [[ -f "$file" ]] || { xml_json_err "FILE_NOT_FOUND" "File not found: $file"; return 1; }
35
+
36
+ # Read first 1KB for analysis
37
+ local header
38
+ header=$(head -c 1024 "$file" 2>/dev/null || head -c 1024 < "$file")
39
+
40
+ # Check for XML signatures
41
+ if echo "$header" | grep -qE '^\s*<\?xml\s+version'; then
42
+ xml_json_ok '{"format":"xml","confidence":"high","signature":"xml_declaration"}'
43
+ return 0
44
+ fi
45
+
46
+ if echo "$header" | grep -qE '^\s*<[a-zA-Z_][a-zA-Z0-9_]*[\s>]'; then
47
+ xml_json_ok '{"format":"xml","confidence":"medium","signature":"root_element"}'
48
+ return 0
49
+ fi
50
+
51
+ # Check for JSON signatures
52
+ if echo "$header" | grep -qE '^\s*(\{|\[)'; then
53
+ # Verify it's valid JSON
54
+ if jq empty "$file" 2>/dev/null; then
55
+ xml_json_ok '{"format":"json","confidence":"high","signature":"valid_json"}'
56
+ return 0
57
+ else
58
+ xml_json_ok '{"format":"json","confidence":"low","signature":"json_like","note":"Invalid JSON syntax"}'
59
+ return 0
60
+ fi
61
+ fi
62
+
63
+ xml_json_ok '{"format":"unknown","confidence":"low","signature":"none"}'
64
+ }
65
+
66
+ # ============================================================================
67
+ # XML to JSON Conversion
68
+ # ============================================================================
69
+
70
+ # xml-to-json: Convert XML to JSON format
71
+ # Usage: xml-to-json <xml_file> [--pretty]
72
+ # Returns: {"ok":true,"result":{"json":"...","format":"object"}}
73
+ xml-to-json() {
74
+ local xml_file="${1:-}"
75
+ local pretty=false
76
+
77
+ # Parse optional arguments
78
+ shift || true
79
+ while [[ $# -gt 0 ]]; do
80
+ case "$1" in
81
+ --pretty) pretty=true; shift ;;
82
+ *) shift ;;
83
+ esac
84
+ done
85
+
86
+ [[ -z "$xml_file" ]] && { xml_json_err "MISSING_ARG" "Missing XML file argument"; return 1; }
87
+ [[ -f "$xml_file" ]] || { xml_json_err "FILE_NOT_FOUND" "XML file not found: $xml_file"; return 1; }
88
+
89
+ # Check well-formedness first
90
+ local well_formed_result
91
+ well_formed_result=$(xml-well-formed "$xml_file" 2>/dev/null)
92
+ if ! echo "$well_formed_result" | jq -e '.result.well_formed' >/dev/null 2>&1; then
93
+ xml_json_err "PARSE_ERROR" "XML is not well-formed"
94
+ return 1
95
+ fi
96
+
97
+ # Try xml2json if available (npm package)
98
+ if [[ "$XML2JSON_AVAILABLE" == "true" ]]; then
99
+ local json_output
100
+ if json_output=$(xml2json "$xml_file" 2>/dev/null); then
101
+ if [[ "$pretty" == "true" ]]; then
102
+ json_output=$(echo "$json_output" | jq '.')
103
+ fi
104
+ local escaped_json
105
+ escaped_json=$(echo "$json_output" | jq -Rs '.')
106
+ xml_json_ok "{\"format\":\"object\",\"json\":$escaped_json}"
107
+ return 0
108
+ fi
109
+ fi
110
+
111
+ # Fallback: Use xsltproc with built-in XSLT
112
+ if [[ "$XSLTPROC_AVAILABLE" == "true" ]]; then
113
+ local xslt_script
114
+ xslt_script=$(cat << 'XSLT'
115
+ <?xml version="1.0"?>
116
+ <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
117
+ <xsl:output method="text"/>
118
+ <xsl:template match="/">
119
+ <xsl:text>{"root":</xsl:text>
120
+ <xsl:apply-templates select="*"/>
121
+ <xsl:text>}</xsl:text>
122
+ </xsl:template>
123
+ <xsl:template match="*">
124
+ <xsl:text>{"</xsl:text>
125
+ <xsl:value-of select="name()"/>
126
+ <xsl:text>":</xsl:text>
127
+ <xsl:choose>
128
+ <xsl:when test="count(*) > 0">
129
+ <xsl:text>[</xsl:text>
130
+ <xsl:apply-templates select="*"/>
131
+ <xsl:text>]</xsl:text>
132
+ </xsl:when>
133
+ <xsl:otherwise>
134
+ <xsl:text>"</xsl:text>
135
+ <xsl:value-of select="."/>
136
+ <xsl:text>"</xsl:text>
137
+ </xsl:otherwise>
138
+ </xsl:choose>
139
+ <xsl:text>}</xsl:text>
140
+ <xsl:if test="position() != last()">,</xsl:if>
141
+ </xsl:template>
142
+ </xsl:stylesheet>
143
+ XSLT
144
+ )
145
+ local json_result
146
+ json_result=$(echo "$xslt_script" | xsltproc - "$xml_file" 2>/dev/null) || {
147
+ xml_json_err "CONVERSION_ERROR" "XSLT conversion failed"
148
+ return 1
149
+ }
150
+ xml_json_ok "{\"format\":\"object\",\"json\":$(echo "$json_result" | jq -Rs '.')}"
151
+ return 0
152
+ fi
153
+
154
+ # Last resort: Use xmlstarlet if available
155
+ if [[ "$XMLSTARLET_AVAILABLE" == "true" ]]; then
156
+ local json_result
157
+ json_result=$(xmlstarlet sel -t -m "/" -o '{"root":{' -m "*" -v "name()" -o ':"' -v "." -o '"' -b -o '}}' "$xml_file" 2>/dev/null) || {
158
+ xml_json_err "CONVERSION_ERROR" "xmlstarlet conversion failed"
159
+ return 1
160
+ }
161
+ xml_json_ok "{\"format\":\"object\",\"json\":$(echo "$json_result" | jq -Rs '.')}"
162
+ return 0
163
+ fi
164
+
165
+ xml_json_err "TOOL_NOT_AVAILABLE" "No XML to JSON conversion tool available. Install xml2json, xsltproc, or xmlstarlet."
166
+ return 1
167
+ }
168
+
169
+ # ============================================================================
170
+ # JSON to XML Conversion
171
+ # ============================================================================
172
+
173
+ # json-to-xml: Convert JSON to XML
174
+ # Usage: json-to-xml <json_file> [root_element]
175
+ # Returns: {"ok":true,"result":{"xml":"<root>...</root>"}}
176
+ json-to-xml() {
177
+ local json_file="${1:-}"
178
+ local root_element="${2:-root}"
179
+
180
+ [[ -z "$json_file" ]] && { xml_json_err "MISSING_ARG" "Missing JSON file argument"; return 1; }
181
+ [[ -f "$json_file" ]] || { xml_json_err "FILE_NOT_FOUND" "JSON file not found: $json_file"; return 1; }
182
+
183
+ # Validate JSON first
184
+ if ! jq empty "$json_file" 2>/dev/null; then
185
+ xml_json_err "PARSE_ERROR" "Invalid JSON file: $json_file"
186
+ return 1
187
+ fi
188
+
189
+ # Build XML using jq to generate structure
190
+ local xml_output
191
+ xml_output=$(jq -r --arg root "$root_element" '
192
+ def to_xml:
193
+ if type == "object" then
194
+ to_entries | map(
195
+ "<\(.key)>\(.value | to_xml)</\(.key)>"
196
+ ) | join("")
197
+ elif type == "array" then
198
+ map("<item>\(. | to_xml)</item>") | join("")
199
+ elif type == "string" then
200
+ .
201
+ elif type == "number" then
202
+ tostring
203
+ elif type == "boolean" then
204
+ tostring
205
+ elif type == "null" then
206
+ ""
207
+ else
208
+ tostring
209
+ end;
210
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<\($root)>\n" + (to_xml) + "\n</\($root)>"
211
+ ' "$json_file" 2>/dev/null) || {
212
+ xml_json_err "CONVERSION_ERROR" "JSON to XML conversion failed"
213
+ return 1
214
+ }
215
+
216
+ # Escape the XML for JSON output
217
+ local escaped_xml
218
+ escaped_xml=$(echo "$xml_output" | jq -Rs '.')
219
+ xml_json_ok "{\"xml\":$escaped_xml}"
220
+ }
221
+
222
+ # ============================================================================
223
+ # XML Document Merging
224
+ # ============================================================================
225
+
226
+ # xml-merge: XInclude document merging
227
+ # Usage: xml-merge <output_file> <main_xml_file>
228
+ # Returns: {"ok":true,"result":{"merged":true,"output":"<path>","sources_resolved":N}}
229
+ xml-merge() {
230
+ local output_file="${1:-}"
231
+ local main_xml="${2:-}"
232
+
233
+ [[ -z "$output_file" ]] && { xml_json_err "MISSING_ARG" "Missing output file argument"; return 1; }
234
+ [[ -z "$main_xml" ]] && { xml_json_err "MISSING_ARG" "Missing main XML file argument"; return 1; }
235
+ [[ -f "$main_xml" ]] || { xml_json_err "FILE_NOT_FOUND" "Main XML file not found: $main_xml"; return 1; }
236
+
237
+ # Check well-formedness of main file
238
+ local well_formed_result
239
+ well_formed_result=$(xml-well-formed "$main_xml" 2>/dev/null)
240
+ if ! echo "$well_formed_result" | jq -e '.result.well_formed' >/dev/null 2>&1; then
241
+ xml_json_err "PARSE_ERROR" "Main XML file is not well-formed"
242
+ return 1
243
+ fi
244
+
245
+ # Use xmllint for XInclude processing with security flags
246
+ if [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
247
+ local merged
248
+ merged=$(xmllint --nonet --noent --xinclude "$main_xml" 2>/dev/null) || {
249
+ xml_json_err "MERGE_ERROR" "XInclude merge failed"
250
+ return 1
251
+ }
252
+
253
+ # Write output
254
+ echo "$merged" > "$output_file"
255
+
256
+ # Count resolved includes (approximate)
257
+ local resolved_count
258
+ resolved_count=$(echo "$merged" | grep -c '<xi:include' 2>/dev/null || echo "0")
259
+
260
+ local escaped_output
261
+ escaped_output=$(echo "$output_file" | jq -Rs '.[:-1]')
262
+ xml_json_ok "{\"merged\":true,\"output\":$escaped_output,\"sources_resolved\":$resolved_count}"
263
+ return 0
264
+ fi
265
+
266
+ # Without xmllint, we cannot safely process XInclude
267
+ xml_json_err "TOOL_NOT_AVAILABLE" "xmllint required for XInclude merging. Install libxml2 utilities."
268
+ return 1
269
+ }
270
+
271
+ # Export functions
272
+ export -f xml-convert-detect-format xml-to-json json-to-xml xml-merge
273
+ export XML2JSON_AVAILABLE