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.
- package/.aether/CONTEXT.md +160 -0
- package/.aether/QUEEN.md +84 -0
- package/.aether/aether-utils.sh +7749 -0
- package/.aether/docs/QUEEN-SYSTEM.md +211 -0
- package/.aether/docs/README.md +68 -0
- package/.aether/docs/caste-system.md +48 -0
- package/.aether/docs/disciplines/DISCIPLINES.md +93 -0
- package/.aether/docs/disciplines/coding-standards.md +197 -0
- package/.aether/docs/disciplines/debugging.md +207 -0
- package/.aether/docs/disciplines/learning.md +254 -0
- package/.aether/docs/disciplines/tdd.md +257 -0
- package/.aether/docs/disciplines/verification-loop.md +167 -0
- package/.aether/docs/disciplines/verification.md +116 -0
- package/.aether/docs/error-codes.md +268 -0
- package/.aether/docs/known-issues.md +233 -0
- package/.aether/docs/pheromones.md +205 -0
- package/.aether/docs/queen-commands.md +97 -0
- package/.aether/exchange/colony-registry.xml +11 -0
- package/.aether/exchange/pheromone-xml.sh +575 -0
- package/.aether/exchange/pheromones.xml +87 -0
- package/.aether/exchange/queen-wisdom.xml +14 -0
- package/.aether/exchange/registry-xml.sh +273 -0
- package/.aether/exchange/wisdom-xml.sh +319 -0
- package/.aether/midden/approach-changes.md +5 -0
- package/.aether/midden/build-failures.md +5 -0
- package/.aether/midden/test-failures.md +5 -0
- package/.aether/model-profiles.yaml +100 -0
- package/.aether/rules/aether-colony.md +134 -0
- package/.aether/schemas/aether-types.xsd +255 -0
- package/.aether/schemas/colony-registry.xsd +309 -0
- package/.aether/schemas/example-prompt-builder.xml +234 -0
- package/.aether/schemas/pheromone.xsd +163 -0
- package/.aether/schemas/prompt.xsd +416 -0
- package/.aether/schemas/queen-wisdom.xsd +325 -0
- package/.aether/schemas/worker-priming.xsd +276 -0
- package/.aether/templates/QUEEN.md.template +79 -0
- package/.aether/templates/colony-state-reset.jq.template +22 -0
- package/.aether/templates/colony-state.template.json +35 -0
- package/.aether/templates/constraints.template.json +9 -0
- package/.aether/templates/crowned-anthill.template.md +36 -0
- package/.aether/templates/handoff-build-error.template.md +30 -0
- package/.aether/templates/handoff-build-success.template.md +39 -0
- package/.aether/templates/handoff.template.md +40 -0
- package/.aether/templates/learning-observations.template.json +6 -0
- package/.aether/templates/midden.template.json +7 -0
- package/.aether/templates/pheromones.template.json +6 -0
- package/.aether/templates/session.template.json +9 -0
- package/.aether/utils/atomic-write.sh +219 -0
- package/.aether/utils/chamber-compare.sh +193 -0
- package/.aether/utils/chamber-utils.sh +297 -0
- package/.aether/utils/colorize-log.sh +132 -0
- package/.aether/utils/error-handler.sh +212 -0
- package/.aether/utils/file-lock.sh +158 -0
- package/.aether/utils/queen-to-md.xsl +395 -0
- package/.aether/utils/semantic-cli.sh +413 -0
- package/.aether/utils/spawn-tree.sh +428 -0
- package/.aether/utils/spawn-with-model.sh +56 -0
- package/.aether/utils/state-loader.sh +215 -0
- package/.aether/utils/swarm-display.sh +268 -0
- package/.aether/utils/watch-spawn-tree.sh +253 -0
- package/.aether/utils/xml-compose.sh +253 -0
- package/.aether/utils/xml-convert.sh +273 -0
- package/.aether/utils/xml-core.sh +186 -0
- package/.aether/utils/xml-query.sh +201 -0
- package/.aether/utils/xml-utils.sh +110 -0
- package/.aether/workers.md +765 -0
- package/.claude/agents/ant/aether-ambassador.md +264 -0
- package/.claude/agents/ant/aether-archaeologist.md +322 -0
- package/.claude/agents/ant/aether-auditor.md +266 -0
- package/.claude/agents/ant/aether-builder.md +187 -0
- package/.claude/agents/ant/aether-chaos.md +268 -0
- package/.claude/agents/ant/aether-chronicler.md +304 -0
- package/.claude/agents/ant/aether-gatekeeper.md +325 -0
- package/.claude/agents/ant/aether-includer.md +373 -0
- package/.claude/agents/ant/aether-keeper.md +271 -0
- package/.claude/agents/ant/aether-measurer.md +317 -0
- package/.claude/agents/ant/aether-probe.md +210 -0
- package/.claude/agents/ant/aether-queen.md +325 -0
- package/.claude/agents/ant/aether-route-setter.md +173 -0
- package/.claude/agents/ant/aether-sage.md +353 -0
- package/.claude/agents/ant/aether-scout.md +142 -0
- package/.claude/agents/ant/aether-surveyor-disciplines.md +416 -0
- package/.claude/agents/ant/aether-surveyor-nest.md +354 -0
- package/.claude/agents/ant/aether-surveyor-pathogens.md +288 -0
- package/.claude/agents/ant/aether-surveyor-provisions.md +359 -0
- package/.claude/agents/ant/aether-tracker.md +265 -0
- package/.claude/agents/ant/aether-watcher.md +244 -0
- package/.claude/agents/ant/aether-weaver.md +247 -0
- package/.claude/commands/ant/archaeology.md +341 -0
- package/.claude/commands/ant/build.md +1160 -0
- package/.claude/commands/ant/chaos.md +349 -0
- package/.claude/commands/ant/colonize.md +270 -0
- package/.claude/commands/ant/continue.md +1070 -0
- package/.claude/commands/ant/council.md +309 -0
- package/.claude/commands/ant/dream.md +265 -0
- package/.claude/commands/ant/entomb.md +487 -0
- package/.claude/commands/ant/feedback.md +78 -0
- package/.claude/commands/ant/flag.md +139 -0
- package/.claude/commands/ant/flags.md +155 -0
- package/.claude/commands/ant/focus.md +58 -0
- package/.claude/commands/ant/help.md +122 -0
- package/.claude/commands/ant/history.md +137 -0
- package/.claude/commands/ant/init.md +409 -0
- package/.claude/commands/ant/interpret.md +267 -0
- package/.claude/commands/ant/lay-eggs.md +201 -0
- package/.claude/commands/ant/maturity.md +102 -0
- package/.claude/commands/ant/memory-details.md +77 -0
- package/.claude/commands/ant/migrate-state.md +165 -0
- package/.claude/commands/ant/oracle.md +387 -0
- package/.claude/commands/ant/organize.md +227 -0
- package/.claude/commands/ant/pause-colony.md +247 -0
- package/.claude/commands/ant/phase.md +126 -0
- package/.claude/commands/ant/plan.md +544 -0
- package/.claude/commands/ant/redirect.md +58 -0
- package/.claude/commands/ant/resume-colony.md +182 -0
- package/.claude/commands/ant/resume.md +363 -0
- package/.claude/commands/ant/seal.md +306 -0
- package/.claude/commands/ant/status.md +272 -0
- package/.claude/commands/ant/swarm.md +361 -0
- package/.claude/commands/ant/tunnels.md +425 -0
- package/.claude/commands/ant/update.md +209 -0
- package/.claude/commands/ant/verify-castes.md +95 -0
- package/.claude/commands/ant/watch.md +238 -0
- package/.opencode/agents/aether-ambassador.md +140 -0
- package/.opencode/agents/aether-archaeologist.md +108 -0
- package/.opencode/agents/aether-auditor.md +144 -0
- package/.opencode/agents/aether-builder.md +184 -0
- package/.opencode/agents/aether-chaos.md +115 -0
- package/.opencode/agents/aether-chronicler.md +122 -0
- package/.opencode/agents/aether-gatekeeper.md +116 -0
- package/.opencode/agents/aether-includer.md +117 -0
- package/.opencode/agents/aether-keeper.md +177 -0
- package/.opencode/agents/aether-measurer.md +128 -0
- package/.opencode/agents/aether-probe.md +133 -0
- package/.opencode/agents/aether-queen.md +286 -0
- package/.opencode/agents/aether-route-setter.md +130 -0
- package/.opencode/agents/aether-sage.md +106 -0
- package/.opencode/agents/aether-scout.md +101 -0
- package/.opencode/agents/aether-surveyor-disciplines.md +386 -0
- package/.opencode/agents/aether-surveyor-nest.md +324 -0
- package/.opencode/agents/aether-surveyor-pathogens.md +259 -0
- package/.opencode/agents/aether-surveyor-provisions.md +329 -0
- package/.opencode/agents/aether-tracker.md +137 -0
- package/.opencode/agents/aether-watcher.md +174 -0
- package/.opencode/agents/aether-weaver.md +130 -0
- package/.opencode/commands/ant/archaeology.md +338 -0
- package/.opencode/commands/ant/build.md +1200 -0
- package/.opencode/commands/ant/chaos.md +346 -0
- package/.opencode/commands/ant/colonize.md +202 -0
- package/.opencode/commands/ant/continue.md +938 -0
- package/.opencode/commands/ant/council.md +305 -0
- package/.opencode/commands/ant/dream.md +262 -0
- package/.opencode/commands/ant/entomb.md +367 -0
- package/.opencode/commands/ant/feedback.md +80 -0
- package/.opencode/commands/ant/flag.md +137 -0
- package/.opencode/commands/ant/flags.md +153 -0
- package/.opencode/commands/ant/focus.md +56 -0
- package/.opencode/commands/ant/help.md +124 -0
- package/.opencode/commands/ant/history.md +127 -0
- package/.opencode/commands/ant/init.md +337 -0
- package/.opencode/commands/ant/interpret.md +256 -0
- package/.opencode/commands/ant/lay-eggs.md +141 -0
- package/.opencode/commands/ant/maturity.md +92 -0
- package/.opencode/commands/ant/memory-details.md +77 -0
- package/.opencode/commands/ant/migrate-state.md +153 -0
- package/.opencode/commands/ant/oracle.md +338 -0
- package/.opencode/commands/ant/organize.md +224 -0
- package/.opencode/commands/ant/pause-colony.md +220 -0
- package/.opencode/commands/ant/phase.md +123 -0
- package/.opencode/commands/ant/plan.md +531 -0
- package/.opencode/commands/ant/redirect.md +67 -0
- package/.opencode/commands/ant/resume-colony.md +178 -0
- package/.opencode/commands/ant/resume.md +363 -0
- package/.opencode/commands/ant/seal.md +247 -0
- package/.opencode/commands/ant/status.md +272 -0
- package/.opencode/commands/ant/swarm.md +357 -0
- package/.opencode/commands/ant/tunnels.md +406 -0
- package/.opencode/commands/ant/update.md +191 -0
- package/.opencode/commands/ant/verify-castes.md +85 -0
- package/.opencode/commands/ant/watch.md +220 -0
- package/.opencode/opencode.json +3 -0
- package/CHANGELOG.md +325 -0
- package/DISCLAIMER.md +74 -0
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/bin/cli.js +2436 -0
- package/bin/generate-commands.sh +291 -0
- package/bin/lib/caste-colors.js +57 -0
- package/bin/lib/colors.js +76 -0
- package/bin/lib/errors.js +255 -0
- package/bin/lib/event-types.js +190 -0
- package/bin/lib/file-lock.js +695 -0
- package/bin/lib/init.js +454 -0
- package/bin/lib/logger.js +242 -0
- package/bin/lib/model-profiles.js +445 -0
- package/bin/lib/model-verify.js +288 -0
- package/bin/lib/nestmate-loader.js +130 -0
- package/bin/lib/proxy-health.js +253 -0
- package/bin/lib/spawn-logger.js +266 -0
- package/bin/lib/state-guard.js +602 -0
- package/bin/lib/state-sync.js +516 -0
- package/bin/lib/telemetry.js +441 -0
- package/bin/lib/update-transaction.js +1454 -0
- package/bin/npx-install.js +178 -0
- package/bin/sync-to-runtime.sh +6 -0
- package/bin/validate-package.sh +88 -0
- 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
|