aether-colony 3.1.17 → 5.0.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/{runtime → .aether}/CONTEXT.md +1 -1
- package/{runtime → .aether}/aether-utils.sh +1772 -98
- package/.aether/docs/QUEEN-SYSTEM.md +211 -0
- package/.aether/docs/QUEEN.md +84 -0
- package/.aether/docs/README.md +68 -0
- package/.aether/docs/caste-system.md +48 -0
- package/{runtime → .aether/docs/disciplines}/DISCIPLINES.md +8 -8
- package/.aether/docs/error-codes.md +268 -0
- package/{runtime → .aether}/docs/known-issues.md +42 -26
- package/.aether/docs/queen-commands.md +97 -0
- package/.aether/exchange/colony-registry.xml +11 -0
- package/{runtime → .aether}/exchange/pheromone-xml.sh +2 -1
- package/.aether/exchange/pheromones.xml +87 -0
- package/.aether/exchange/queen-wisdom.xml +14 -0
- package/{runtime → .aether}/exchange/registry-xml.sh +7 -3
- package/{runtime → .aether}/exchange/wisdom-xml.sh +11 -4
- package/.aether/rules/aether-colony.md +134 -0
- package/.aether/schemas/example-prompt-builder.xml +234 -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/{runtime → .aether}/utils/atomic-write.sh +5 -5
- package/{runtime → .aether}/utils/chamber-compare.sh +23 -10
- package/{runtime → .aether}/utils/chamber-utils.sh +32 -20
- package/{runtime → .aether}/utils/error-handler.sh +13 -1
- package/{runtime → .aether}/utils/file-lock.sh +49 -13
- package/.aether/utils/semantic-cli.sh +413 -0
- package/{runtime → .aether}/utils/xml-compose.sh +7 -1
- package/.aether/utils/xml-convert.sh +273 -0
- package/.aether/utils/xml-query.sh +201 -0
- package/.aether/utils/xml-utils.sh +110 -0
- package/{runtime → .aether}/workers.md +14 -17
- 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 +16 -7
- package/.claude/commands/ant/build.md +415 -284
- package/.claude/commands/ant/chaos.md +19 -10
- package/.claude/commands/ant/colonize.md +58 -24
- package/.claude/commands/ant/continue.md +155 -145
- package/.claude/commands/ant/council.md +15 -5
- package/.claude/commands/ant/dream.md +16 -7
- package/.claude/commands/ant/entomb.md +274 -157
- package/.claude/commands/ant/feedback.md +33 -29
- package/.claude/commands/ant/flag.md +18 -10
- package/.claude/commands/ant/flags.md +14 -6
- package/.claude/commands/ant/focus.md +29 -21
- package/.claude/commands/ant/help.md +11 -1
- package/.claude/commands/ant/history.md +10 -0
- package/.claude/commands/ant/init.md +91 -65
- package/.claude/commands/ant/interpret.md +15 -4
- package/.claude/commands/ant/lay-eggs.md +55 -7
- package/.claude/commands/ant/maturity.md +11 -1
- package/.claude/commands/ant/migrate-state.md +14 -2
- package/.claude/commands/ant/oracle.md +23 -15
- package/.claude/commands/ant/organize.md +29 -20
- package/.claude/commands/ant/pause-colony.md +17 -7
- package/.claude/commands/ant/phase.md +17 -8
- package/.claude/commands/ant/plan.md +20 -9
- package/.claude/commands/ant/redirect.md +29 -32
- package/.claude/commands/ant/resume-colony.md +19 -9
- package/.claude/commands/ant/resume.md +272 -96
- package/.claude/commands/ant/seal.md +201 -191
- package/.claude/commands/ant/status.md +71 -32
- package/.claude/commands/ant/swarm.md +26 -44
- package/.claude/commands/ant/tunnels.md +279 -105
- package/.claude/commands/ant/update.md +81 -20
- package/.claude/commands/ant/verify-castes.md +14 -4
- package/.claude/commands/ant/watch.md +13 -12
- package/.opencode/agents/aether-ambassador.md +63 -20
- package/.opencode/agents/aether-archaeologist.md +29 -12
- package/.opencode/agents/aether-auditor.md +51 -18
- package/.opencode/agents/aether-builder.md +69 -19
- package/.opencode/agents/aether-chaos.md +29 -12
- package/.opencode/agents/aether-chronicler.md +60 -18
- package/.opencode/agents/aether-gatekeeper.md +27 -18
- package/.opencode/agents/aether-includer.md +27 -18
- package/.opencode/agents/aether-keeper.md +89 -18
- package/.opencode/agents/aether-measurer.md +27 -18
- package/.opencode/agents/aether-probe.md +60 -18
- package/.opencode/agents/aether-queen.md +172 -24
- package/.opencode/agents/aether-route-setter.md +57 -12
- package/.opencode/agents/aether-sage.md +26 -18
- package/.opencode/agents/aether-scout.md +27 -19
- package/.opencode/agents/aether-surveyor-disciplines.md +53 -1
- package/.opencode/agents/aether-surveyor-nest.md +53 -1
- package/.opencode/agents/aether-surveyor-pathogens.md +51 -1
- package/.opencode/agents/aether-surveyor-provisions.md +53 -1
- package/.opencode/agents/aether-tracker.md +64 -18
- package/.opencode/agents/aether-watcher.md +66 -19
- package/.opencode/agents/aether-weaver.md +61 -18
- package/.opencode/commands/ant/build.md +406 -192
- package/.opencode/commands/ant/continue.md +66 -76
- package/.opencode/commands/ant/entomb.md +106 -45
- package/.opencode/commands/ant/init.md +46 -48
- package/.opencode/commands/ant/organize.md +5 -5
- package/.opencode/commands/ant/resume.md +334 -0
- package/.opencode/commands/ant/seal.md +33 -24
- package/.opencode/commands/ant/status.md +11 -0
- package/.opencode/commands/ant/tunnels.md +149 -0
- package/.opencode/commands/ant/update.md +59 -16
- package/CHANGELOG.md +79 -0
- package/README.md +135 -353
- package/bin/cli.js +243 -122
- package/bin/generate-commands.sh +2 -2
- package/bin/lib/init.js +13 -3
- package/bin/lib/update-transaction.js +119 -117
- package/bin/sync-to-runtime.sh +5 -137
- package/bin/validate-package.sh +84 -0
- package/package.json +9 -6
- package/.opencode/agents/aether-architect.md +0 -66
- package/.opencode/agents/aether-guardian.md +0 -107
- package/.opencode/agents/workers.md +0 -1034
- package/runtime/QUEEN_ANT_ARCHITECTURE.md +0 -402
- package/runtime/data/signatures.json +0 -41
- package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +0 -1343
- package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +0 -2642
- package/runtime/docs/PHEROMONE-INJECTION.md +0 -240
- package/runtime/docs/PHEROMONE-INTEGRATION.md +0 -192
- package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +0 -426
- package/runtime/docs/README.md +0 -94
- package/runtime/docs/VISUAL-OUTPUT-SPEC.md +0 -219
- package/runtime/docs/biological-reference.md +0 -272
- package/runtime/docs/codebase-review.md +0 -399
- package/runtime/docs/command-sync.md +0 -164
- package/runtime/docs/constraints.md +0 -116
- package/runtime/docs/implementation-learnings.md +0 -89
- package/runtime/docs/namespace.md +0 -148
- package/runtime/docs/pathogen-schema-example.json +0 -36
- package/runtime/docs/pathogen-schema.md +0 -111
- package/runtime/docs/planning-discipline.md +0 -159
- package/runtime/docs/progressive-disclosure.md +0 -184
- package/runtime/lib/queen-utils.sh +0 -729
- package/runtime/planning.md +0 -159
- package/runtime/recover.sh +0 -136
- package/runtime/utils/xml-utils.sh +0 -2196
- package/runtime/workers-new-castes.md +0 -516
- /package/{runtime → .aether/docs/disciplines}/coding-standards.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/debugging.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/learning.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/tdd.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/verification-loop.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/verification.md +0 -0
- /package/{runtime → .aether}/docs/pheromones.md +0 -0
- /package/{runtime → .aether}/model-profiles.yaml +0 -0
- /package/{runtime → .aether}/schemas/aether-types.xsd +0 -0
- /package/{runtime → .aether}/schemas/colony-registry.xsd +0 -0
- /package/{runtime → .aether}/schemas/pheromone.xsd +0 -0
- /package/{runtime → .aether}/schemas/prompt.xsd +0 -0
- /package/{runtime → .aether}/schemas/queen-wisdom.xsd +0 -0
- /package/{runtime → .aether}/schemas/worker-priming.xsd +0 -0
- /package/{runtime → .aether}/templates/QUEEN.md.template +0 -0
- /package/{runtime → .aether}/utils/colorize-log.sh +0 -0
- /package/{runtime → .aether}/utils/queen-to-md.xsl +0 -0
- /package/{runtime → .aether}/utils/spawn-tree.sh +0 -0
- /package/{runtime → .aether}/utils/spawn-with-model.sh +0 -0
- /package/{runtime → .aether}/utils/state-loader.sh +0 -0
- /package/{runtime → .aether}/utils/swarm-display.sh +0 -0
- /package/{runtime → .aether}/utils/watch-spawn-tree.sh +0 -0
- /package/{runtime → .aether}/utils/xml-core.sh +0 -0
|
@@ -1,2196 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# XML Utilities Loader for Aether Colony
|
|
3
|
-
# Modular architecture - sources xml-core.sh and xml-compose.sh
|
|
4
|
-
#
|
|
5
|
-
# Usage: source .aether/utils/xml-utils.sh
|
|
6
|
-
# xml-validate <xml_file> <xsd_file>
|
|
7
|
-
# xml-to-json <xml_file>
|
|
8
|
-
# json-to-xml <json_file> [root_element]
|
|
9
|
-
# xml-query <xml_file> <xpath_expression>
|
|
10
|
-
# xml-merge <output_file> <input_files...>
|
|
11
|
-
#
|
|
12
|
-
# All functions return JSON status like other aether-utils
|
|
13
|
-
#
|
|
14
|
-
# Note: This file loads xml-core.sh and xml-compose.sh for modularity.
|
|
15
|
-
# The actual implementations are in those modules; this file provides
|
|
16
|
-
# backward compatibility and loads additional domain functions below.
|
|
17
|
-
|
|
18
|
-
set -euo pipefail
|
|
19
|
-
|
|
20
|
-
# Determine script directory for sourcing modules
|
|
21
|
-
# Handle case when sourced interactively (BASH_SOURCE[0] may be empty)
|
|
22
|
-
if [[ -n "${BASH_SOURCE[0]:-}" ]]; then
|
|
23
|
-
_XML_UTILS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
-
else
|
|
25
|
-
# Fallback: derive from the sourced script's location
|
|
26
|
-
_XML_UTILS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
# ============================================================================
|
|
30
|
-
# Load Modular Components
|
|
31
|
-
# ============================================================================
|
|
32
|
-
|
|
33
|
-
# Core utilities (validation, formatting, escaping)
|
|
34
|
-
[[ -f "$_XML_UTILS_DIR/xml-core.sh" ]] && source "$_XML_UTILS_DIR/xml-core.sh"
|
|
35
|
-
|
|
36
|
-
# XInclude composition
|
|
37
|
-
[[ -f "$_XML_UTILS_DIR/xml-compose.sh" ]] && source "$_XML_UTILS_DIR/xml-compose.sh"
|
|
38
|
-
|
|
39
|
-
# ============================================================================
|
|
40
|
-
# Feature Detection (supplement xml-core.sh if not already set)
|
|
41
|
-
# ============================================================================
|
|
42
|
-
|
|
43
|
-
# Check for required XML tools
|
|
44
|
-
: "${XMLLINT_AVAILABLE:=false}"
|
|
45
|
-
: "${XMLSTARLET_AVAILABLE:=false}"
|
|
46
|
-
: "${XSLTPROC_AVAILABLE:=false}"
|
|
47
|
-
: "${XML2JSON_AVAILABLE:=false}"
|
|
48
|
-
|
|
49
|
-
if command -v xmllint >/dev/null 2>&1; then
|
|
50
|
-
XMLLINT_AVAILABLE=true
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
if command -v xmlstarlet >/dev/null 2>&1; then
|
|
54
|
-
XMLSTARLET_AVAILABLE=true
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
if command -v xsltproc >/dev/null 2>&1; then
|
|
58
|
-
XSLTPROC_AVAILABLE=true
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
if command -v xml2json >/dev/null 2>&1; then
|
|
62
|
-
XML2JSON_AVAILABLE=true
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
# ============================================================================
|
|
66
|
-
# Additional JSON Output Helpers (supplement xml-core.sh)
|
|
67
|
-
# ============================================================================
|
|
68
|
-
|
|
69
|
-
# Use xml-core.sh versions if available, otherwise define here
|
|
70
|
-
if ! type xml_json_ok &>/dev/null; then
|
|
71
|
-
xml_json_ok() { printf '{"ok":true,"result":%s}\n' "$1"; }
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
if ! type xml_json_err &>/dev/null; then
|
|
75
|
-
xml_json_err() {
|
|
76
|
-
local message="${2:-$1}"
|
|
77
|
-
printf '{"ok":false,"error":"%s"}\n' "$message" >&2
|
|
78
|
-
return 1
|
|
79
|
-
}
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
# ============================================================================
|
|
83
|
-
# Domain Functions: Pheromones, Wisdom, Registry, Prompts
|
|
84
|
-
# ============================================================================
|
|
85
|
-
|
|
86
|
-
# These functions remain in xml-utils.sh (not yet modularized)
|
|
87
|
-
|
|
88
|
-
# ============================================================================
|
|
89
|
-
# Core XML Functions
|
|
90
|
-
# ============================================================================
|
|
91
|
-
|
|
92
|
-
# xml-validate: Validate XML against XSD schema using xmllint
|
|
93
|
-
# Usage: xml-validate <xml_file> <xsd_file>
|
|
94
|
-
# Returns: {"ok":true,"result":{"valid":true,"errors":[]}} or error
|
|
95
|
-
xml-validate() {
|
|
96
|
-
local xml_file="${1:-}"
|
|
97
|
-
local xsd_file="${2:-}"
|
|
98
|
-
|
|
99
|
-
# Validate arguments
|
|
100
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
101
|
-
[[ -z "$xsd_file" ]] && { xml_json_err "Missing XSD schema file argument"; return 1; }
|
|
102
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
103
|
-
[[ -f "$xsd_file" ]] || { xml_json_err "XSD schema file not found: $xsd_file"; return 1; }
|
|
104
|
-
|
|
105
|
-
# Check for xmllint
|
|
106
|
-
if [[ "$XMLLINT_AVAILABLE" != "true" ]]; then
|
|
107
|
-
xml_json_err "xmllint not available. Install libxml2 utilities."
|
|
108
|
-
return 1
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# Validate XML against XSD (with XXE protection)
|
|
112
|
-
local errors
|
|
113
|
-
errors=$(xmllint --nonet --noent --noout --schema "$xsd_file" "$xml_file" 2>&1) && {
|
|
114
|
-
xml_json_ok '{"valid":true,"errors":[]}'
|
|
115
|
-
return 0
|
|
116
|
-
} || {
|
|
117
|
-
# Parse errors into JSON array
|
|
118
|
-
local error_json
|
|
119
|
-
error_json=$(echo "$errors" | jq -R -s 'split("\n") | map(select(length > 0))')
|
|
120
|
-
xml_json_ok "{\"valid\":false,\"errors\":$error_json}"
|
|
121
|
-
return 0
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
# xml-well-formed: Check if XML is well-formed (no schema validation)
|
|
126
|
-
# Usage: xml-well-formed <xml_file>
|
|
127
|
-
# Returns: {"ok":true,"result":{"well_formed":true,"error":null}} or error details
|
|
128
|
-
xml-well-formed() {
|
|
129
|
-
local xml_file="${1:-}"
|
|
130
|
-
|
|
131
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
132
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
133
|
-
|
|
134
|
-
if [[ "$XMLLINT_AVAILABLE" != "true" ]]; then
|
|
135
|
-
xml_json_err "xmllint not available. Install libxml2 utilities."
|
|
136
|
-
return 1
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
local error
|
|
140
|
-
error=$(xmllint --nonet --noent --noout "$xml_file" 2>&1) && {
|
|
141
|
-
xml_json_ok '{"well_formed":true,"error":null}'
|
|
142
|
-
return 0
|
|
143
|
-
} || {
|
|
144
|
-
local escaped_error
|
|
145
|
-
escaped_error=$(echo "$error" | jq -Rs '.[:-1]')
|
|
146
|
-
xml_json_ok "{\"well_formed\":false,\"error\":$escaped_error}"
|
|
147
|
-
return 0
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
# xml-to-json: Convert XML to JSON using available tools
|
|
152
|
-
# Usage: xml-to-json <xml_file> [options]
|
|
153
|
-
# Options: --pretty (pretty print output)
|
|
154
|
-
# Returns: {"ok":true,"result":<json_object>}
|
|
155
|
-
xml-to-json() {
|
|
156
|
-
local xml_file="${1:-}"
|
|
157
|
-
local pretty=false
|
|
158
|
-
|
|
159
|
-
# Parse optional arguments
|
|
160
|
-
shift || true
|
|
161
|
-
while [[ $# -gt 0 ]]; do
|
|
162
|
-
case "$1" in
|
|
163
|
-
--pretty) pretty=true; shift ;;
|
|
164
|
-
*) shift ;;
|
|
165
|
-
esac
|
|
166
|
-
done
|
|
167
|
-
|
|
168
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
169
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
170
|
-
|
|
171
|
-
# Check well-formedness first
|
|
172
|
-
local well_formed_result
|
|
173
|
-
well_formed_result=$(xml-well-formed "$xml_file" 2>/dev/null)
|
|
174
|
-
if ! echo "$well_formed_result" | jq -e '.result.well_formed' >/dev/null 2>&1; then
|
|
175
|
-
xml_json_err "XML is not well-formed"
|
|
176
|
-
return 1
|
|
177
|
-
fi
|
|
178
|
-
|
|
179
|
-
# Try xml2json if available (npm package)
|
|
180
|
-
if [[ "$XML2JSON_AVAILABLE" == "true" ]]; then
|
|
181
|
-
local json_output
|
|
182
|
-
if json_output=$(xml2json "$xml_file" 2>/dev/null); then
|
|
183
|
-
if [[ "$pretty" == "true" ]]; then
|
|
184
|
-
json_output=$(echo "$json_output" | jq '.')
|
|
185
|
-
fi
|
|
186
|
-
xml_json_ok "$(echo "$json_output" | jq -Rs '.[:-1]')"
|
|
187
|
-
return 0
|
|
188
|
-
fi
|
|
189
|
-
fi
|
|
190
|
-
|
|
191
|
-
# Fallback: Use xsltproc with built-in XSLT if available
|
|
192
|
-
if [[ "$XSLTPROC_AVAILABLE" == "true" ]]; then
|
|
193
|
-
local xslt_script
|
|
194
|
-
xslt_script=$(cat << 'XSLT'
|
|
195
|
-
<?xml version="1.0"?>
|
|
196
|
-
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
|
197
|
-
<xsl:output method="text"/>
|
|
198
|
-
<xsl:template match="/">
|
|
199
|
-
<xsl:text>{"root":</xsl:text>
|
|
200
|
-
<xsl:apply-templates select="*"/>
|
|
201
|
-
<xsl:text>}</xsl:text>
|
|
202
|
-
</xsl:template>
|
|
203
|
-
<xsl:template match="*">
|
|
204
|
-
<xsl:text>{"</xsl:text>
|
|
205
|
-
<xsl:value-of select="name()"/>
|
|
206
|
-
<xsl:text>":</xsl:text>
|
|
207
|
-
<xsl:choose>
|
|
208
|
-
<xsl:when test="count(*) > 0">
|
|
209
|
-
<xsl:text>[</xsl:text>
|
|
210
|
-
<xsl:apply-templates select="*"/>
|
|
211
|
-
<xsl:text>]</xsl:text>
|
|
212
|
-
</xsl:when>
|
|
213
|
-
<xsl:otherwise>
|
|
214
|
-
<xsl:text>"</xsl:text>
|
|
215
|
-
<xsl:value-of select="."/>
|
|
216
|
-
<xsl:text>"</xsl:text>
|
|
217
|
-
</xsl:otherwise>
|
|
218
|
-
</xsl:choose>
|
|
219
|
-
<xsl:text>}</xsl:text>
|
|
220
|
-
<xsl:if test="position() != last()">,</xsl:if>
|
|
221
|
-
</xsl:template>
|
|
222
|
-
</xsl:stylesheet>
|
|
223
|
-
XSLT
|
|
224
|
-
)
|
|
225
|
-
local json_result
|
|
226
|
-
json_result=$(echo "$xslt_script" | xsltproc - "$xml_file" 2>/dev/null) || {
|
|
227
|
-
xml_json_err "XSLT conversion failed"
|
|
228
|
-
return 1
|
|
229
|
-
}
|
|
230
|
-
xml_json_ok "$json_result"
|
|
231
|
-
return 0
|
|
232
|
-
fi
|
|
233
|
-
|
|
234
|
-
# Last resort: Use xmlstarlet if available
|
|
235
|
-
if [[ "$XMLSTARLET_AVAILABLE" == "true" ]]; then
|
|
236
|
-
# xmlstarlet can convert to various formats, we'll use sel to extract structure
|
|
237
|
-
local json_result
|
|
238
|
-
json_result=$(xmlstarlet sel -t -m "/" -o '{"root":{' -m "*" -v "name()" -o ':"' -v "." -o '"' -b -o '}}' "$xml_file" 2>/dev/null) || {
|
|
239
|
-
xml_json_err "xmlstarlet conversion failed"
|
|
240
|
-
return 1
|
|
241
|
-
}
|
|
242
|
-
xml_json_ok "$json_result"
|
|
243
|
-
return 0
|
|
244
|
-
fi
|
|
245
|
-
|
|
246
|
-
xml_json_err "No XML to JSON conversion tool available. Install xml2json, xsltproc, or xmlstarlet."
|
|
247
|
-
return 1
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
# json-to-xml: Convert JSON to XML
|
|
251
|
-
# Usage: json-to-xml <json_file> [root_element]
|
|
252
|
-
# Returns: {"ok":true,"result":{"xml":"<root>...</root>"}}
|
|
253
|
-
json-to-xml() {
|
|
254
|
-
local json_file="${1:-}"
|
|
255
|
-
local root_element="${2:-root}"
|
|
256
|
-
|
|
257
|
-
[[ -z "$json_file" ]] && { xml_json_err "Missing JSON file argument"; return 1; }
|
|
258
|
-
[[ -f "$json_file" ]] || { xml_json_err "JSON file not found: $json_file"; return 1; }
|
|
259
|
-
|
|
260
|
-
# Validate JSON first
|
|
261
|
-
if ! jq empty "$json_file" 2>/dev/null; then
|
|
262
|
-
xml_json_err "Invalid JSON file: $json_file"
|
|
263
|
-
return 1
|
|
264
|
-
fi
|
|
265
|
-
|
|
266
|
-
# Build XML using jq to generate structure
|
|
267
|
-
local xml_output
|
|
268
|
-
xml_output=$(jq -r --arg root "$root_element" '
|
|
269
|
-
def to_xml:
|
|
270
|
-
if type == "object" then
|
|
271
|
-
to_entries | map(
|
|
272
|
-
"<\(.key)>\(.value | to_xml)</\(.key)>"
|
|
273
|
-
) | join("")
|
|
274
|
-
elif type == "array" then
|
|
275
|
-
map("<item>\(. | to_xml)</item>") | join("")
|
|
276
|
-
elif type == "string" then
|
|
277
|
-
.
|
|
278
|
-
elif type == "number" then
|
|
279
|
-
tostring
|
|
280
|
-
elif type == "boolean" then
|
|
281
|
-
tostring
|
|
282
|
-
elif type == "null" then
|
|
283
|
-
""
|
|
284
|
-
else
|
|
285
|
-
tostring
|
|
286
|
-
end;
|
|
287
|
-
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<\($root)>\n" + (to_xml) + "\n</\($root)>"
|
|
288
|
-
' "$json_file" 2>/dev/null) || {
|
|
289
|
-
xml_json_err "JSON to XML conversion failed"
|
|
290
|
-
return 1
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
# Escape the XML for JSON output
|
|
294
|
-
local escaped_xml
|
|
295
|
-
escaped_xml=$(echo "$xml_output" | jq -Rs '.')
|
|
296
|
-
xml_json_ok "{\"xml\":$escaped_xml}"
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
# xml-query: XPath query function using XMLStarlet
|
|
300
|
-
# Usage: xml-query <xml_file> <xpath_expression>
|
|
301
|
-
# Returns: {"ok":true,"result":{"matches":[...],"count":N}}
|
|
302
|
-
xml-query() {
|
|
303
|
-
local xml_file="${1:-}"
|
|
304
|
-
local xpath="${2:-}"
|
|
305
|
-
|
|
306
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
307
|
-
[[ -z "$xpath" ]] && { xml_json_err "Missing XPath expression argument"; return 1; }
|
|
308
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
309
|
-
|
|
310
|
-
if [[ "$XMLSTARLET_AVAILABLE" != "true" ]]; then
|
|
311
|
-
xml_json_err "xmlstarlet not available. Install xmlstarlet for XPath queries."
|
|
312
|
-
return 1
|
|
313
|
-
fi
|
|
314
|
-
|
|
315
|
-
# Execute XPath query
|
|
316
|
-
local results
|
|
317
|
-
results=$(xmlstarlet sel -t -m "$xpath" -v "." -n "$xml_file" 2>/dev/null) || {
|
|
318
|
-
xml_json_err "XPath query failed: $xpath"
|
|
319
|
-
return 1
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
# Convert results to JSON array
|
|
323
|
-
local json_array
|
|
324
|
-
json_array=$(echo "$results" | jq -R -s 'split("\n") | map(select(length > 0))')
|
|
325
|
-
local count
|
|
326
|
-
count=$(echo "$json_array" | jq 'length')
|
|
327
|
-
|
|
328
|
-
xml_json_ok "{\"matches\":$json_array,\"count\":$count}"
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
# xml-query-attr: Query for specific attribute values
|
|
332
|
-
# Usage: xml-query-attr <xml_file> <element> <attribute>
|
|
333
|
-
# Returns: {"ok":true,"result":{"attributes":[...],"count":N}}
|
|
334
|
-
xml-query-attr() {
|
|
335
|
-
local xml_file="${1:-}"
|
|
336
|
-
local element="${2:-}"
|
|
337
|
-
local attr="${3:-}"
|
|
338
|
-
|
|
339
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
340
|
-
[[ -z "$element" ]] && { xml_json_err "Missing element argument"; return 1; }
|
|
341
|
-
[[ -z "$attr" ]] && { xml_json_err "Missing attribute argument"; return 1; }
|
|
342
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
343
|
-
|
|
344
|
-
if [[ "$XMLSTARLET_AVAILABLE" != "true" ]]; then
|
|
345
|
-
xml_json_err "xmlstarlet not available. Install xmlstarlet for attribute queries."
|
|
346
|
-
return 1
|
|
347
|
-
fi
|
|
348
|
-
|
|
349
|
-
local results
|
|
350
|
-
results=$(xmlstarlet sel -t -m "//$element" -v "@$attr" -n "$xml_file" 2>/dev/null) || {
|
|
351
|
-
xml_json_err "Attribute query failed: $element/@$attr"
|
|
352
|
-
return 1
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
local json_array
|
|
356
|
-
json_array=$(echo "$results" | jq -R -s 'split("\n") | map(select(length > 0))')
|
|
357
|
-
local count
|
|
358
|
-
count=$(echo "$json_array" | jq 'length')
|
|
359
|
-
|
|
360
|
-
xml_json_ok "{\"attributes\":$json_array,\"count\":$count}"
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
# xml-merge: XInclude document merging
|
|
364
|
-
# Usage: xml-merge <output_file> <main_xml_file> [included_files...]
|
|
365
|
-
# Returns: {"ok":true,"result":{"merged":true,"output":"<path>"}}
|
|
366
|
-
xml-merge() {
|
|
367
|
-
local output_file="${1:-}"
|
|
368
|
-
local main_xml="${2:-}"
|
|
369
|
-
|
|
370
|
-
[[ -z "$output_file" ]] && { xml_json_err "Missing output file argument"; return 1; }
|
|
371
|
-
[[ -z "$main_xml" ]] && { xml_json_err "Missing main XML file argument"; return 1; }
|
|
372
|
-
[[ -f "$main_xml" ]] || { xml_json_err "Main XML file not found: $main_xml"; return 1; }
|
|
373
|
-
|
|
374
|
-
# Check well-formedness of main file
|
|
375
|
-
local well_formed_result
|
|
376
|
-
well_formed_result=$(xml-well-formed "$main_xml" 2>/dev/null)
|
|
377
|
-
if ! echo "$well_formed_result" | jq -e '.result.well_formed' >/dev/null 2>&1; then
|
|
378
|
-
xml_json_err "Main XML file is not well-formed"
|
|
379
|
-
return 1
|
|
380
|
-
fi
|
|
381
|
-
|
|
382
|
-
# Use xmllint for XInclude processing if available
|
|
383
|
-
if [[ "$XMLLINT_AVAILABLE" == "true" ]]; then
|
|
384
|
-
local merged
|
|
385
|
-
merged=$(xmllint --nonet --noent --xinclude "$main_xml" 2>/dev/null) || {
|
|
386
|
-
xml_json_err "XInclude merge failed"
|
|
387
|
-
return 1
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
# Write output
|
|
391
|
-
echo "$merged" > "$output_file"
|
|
392
|
-
local escaped_output
|
|
393
|
-
escaped_output=$(echo "$output_file" | jq -Rs '.[:-1]')
|
|
394
|
-
xml_json_ok "{\"merged\":true,\"output\":$escaped_output}"
|
|
395
|
-
return 0
|
|
396
|
-
fi
|
|
397
|
-
|
|
398
|
-
# Fallback: Simple file concatenation with root element wrapping
|
|
399
|
-
# This is a basic implementation - full XInclude requires xmllint
|
|
400
|
-
local temp_dir
|
|
401
|
-
temp_dir=$(mktemp -d)
|
|
402
|
-
|
|
403
|
-
# Extract root element from main file
|
|
404
|
-
local root_element
|
|
405
|
-
root_element=$(grep -oP '(?<=<)[^>\s?/]+' "$main_xml" | head -1)
|
|
406
|
-
|
|
407
|
-
{
|
|
408
|
-
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
|
409
|
-
echo "<$root_element>"
|
|
410
|
-
cat "$main_xml" | sed '1,/<'$root_element'>/d' | sed '/<\/'$root_element'>/,$d'
|
|
411
|
-
echo "</$root_element>"
|
|
412
|
-
} > "$output_file"
|
|
413
|
-
|
|
414
|
-
rm -rf "$temp_dir"
|
|
415
|
-
|
|
416
|
-
local escaped_output
|
|
417
|
-
escaped_output=$(echo "$output_file" | jq -Rs '.[:-1]')
|
|
418
|
-
xml_json_ok "{\"merged\":true,\"output\":$escaped_output,\"note\":\"Basic merge without XInclude\"}"
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
# xml-format: Pretty-print XML file
|
|
422
|
-
# Usage: xml-format <xml_file> [output_file]
|
|
423
|
-
# Returns: {"ok":true,"result":{"formatted":true,"output":"<path>"}}
|
|
424
|
-
xml-format() {
|
|
425
|
-
local xml_file="${1:-}"
|
|
426
|
-
local output_file="${2:-}"
|
|
427
|
-
|
|
428
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
429
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
430
|
-
|
|
431
|
-
if [[ "$XMLLINT_AVAILABLE" != "true" ]]; then
|
|
432
|
-
xml_json_err "xmllint not available. Install libxml2 utilities."
|
|
433
|
-
return 1
|
|
434
|
-
fi
|
|
435
|
-
|
|
436
|
-
# Determine output destination
|
|
437
|
-
local target="${output_file:-$xml_file}"
|
|
438
|
-
|
|
439
|
-
# Format XML with proper indentation (with XXE protection)
|
|
440
|
-
local formatted
|
|
441
|
-
formatted=$(xmllint --nonet --noent --format "$xml_file" 2>/dev/null) || {
|
|
442
|
-
xml_json_err "XML formatting failed"
|
|
443
|
-
return 1
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
echo "$formatted" > "$target"
|
|
447
|
-
|
|
448
|
-
local escaped_output
|
|
449
|
-
escaped_output=$(echo "$target" | jq -Rs '.[:-1]')
|
|
450
|
-
xml_json_ok "{\"formatted\":true,\"output\":$escaped_output}"
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
# xml-escape: Escape special characters for XML content
|
|
454
|
-
# Usage: xml-escape "string with <special> & characters"
|
|
455
|
-
# Returns: {"ok":true,"result":"escaped string"}
|
|
456
|
-
xml-escape() {
|
|
457
|
-
local input="${1:-}"
|
|
458
|
-
|
|
459
|
-
# Escape XML special characters
|
|
460
|
-
local escaped
|
|
461
|
-
escaped=$(echo "$input" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g; s/'"'"'/\'/g')
|
|
462
|
-
|
|
463
|
-
local escaped_json
|
|
464
|
-
escaped_json=$(echo "$escaped" | jq -Rs '.[:-1]')
|
|
465
|
-
xml_json_ok "$escaped_json"
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
# xml-unescape: Unescape XML entities
|
|
469
|
-
# Usage: xml-unescape "string with <special> entities"
|
|
470
|
-
# Returns: {"ok":true,"result":"unescaped string"}
|
|
471
|
-
xml-unescape() {
|
|
472
|
-
local input="${1:-}"
|
|
473
|
-
|
|
474
|
-
# Unescape XML entities
|
|
475
|
-
local unescaped
|
|
476
|
-
unescaped=$(echo "$input" | sed 's/\</</g; s/\>/>/g; s/\"/"/g; s/\'/'"'"'/g; s/\&/\&/g')
|
|
477
|
-
|
|
478
|
-
local unescaped_json
|
|
479
|
-
unescaped_json=$(echo "$unescaped" | jq -Rs '.[:-1]')
|
|
480
|
-
xml_json_ok "$unescaped_json"
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
# xml-detect-tools: Detect available XML tools
|
|
484
|
-
# Usage: xml-detect-tools
|
|
485
|
-
# Returns: {"ok":true,"result":{"xmllint":true,"xmlstarlet":false,...}}
|
|
486
|
-
xml-detect-tools() {
|
|
487
|
-
xml_json_ok "{\"xmllint\":$XMLLINT_AVAILABLE,\"xmlstarlet\":$XMLSTARLET_AVAILABLE,\"xsltproc\":$XSLTPROC_AVAILABLE,\"xml2json\":$XML2JSON_AVAILABLE}"
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
# ============================================================================
|
|
491
|
-
# Pheromone Exchange Format (Hybrid JSON/XML)
|
|
492
|
-
# ============================================================================
|
|
493
|
-
|
|
494
|
-
# pheromone-to-xml: Convert pheromone JSON to XML format with full XSD schema support
|
|
495
|
-
# Usage: pheromone-to-xml <pheromone_json_file> [output_xml_file] [xsd_schema_file]
|
|
496
|
-
# pheromone_json_file: Path to pheromone JSON (supports both single signal and full pheromones format)
|
|
497
|
-
# output_xml_file: Optional path to write XML output (if omitted, returns XML in result)
|
|
498
|
-
# xsd_schema_file: Optional path to XSD schema for validation (default: .aether/schemas/pheromone.xsd)
|
|
499
|
-
# Returns: {"ok":true,"result":{"xml":"<pheromones>...</pheromones>","validated":true,"path":"..."}}
|
|
500
|
-
pheromone-to-xml() {
|
|
501
|
-
local json_file="${1:-}"
|
|
502
|
-
local output_xml="${2:-}"
|
|
503
|
-
local xsd_file="${3:-.aether/schemas/pheromone.xsd}"
|
|
504
|
-
|
|
505
|
-
[[ -z "$json_file" ]] && { xml_json_err "Missing JSON file argument"; return 1; }
|
|
506
|
-
[[ -f "$json_file" ]] || { xml_json_err "JSON file not found: $json_file"; return 1; }
|
|
507
|
-
|
|
508
|
-
# Validate JSON
|
|
509
|
-
if ! jq empty "$json_file" 2>/dev/null; then
|
|
510
|
-
xml_json_err "Invalid JSON file: $json_file"
|
|
511
|
-
return 1
|
|
512
|
-
fi
|
|
513
|
-
|
|
514
|
-
# Detect JSON format: single signal or full pheromones structure
|
|
515
|
-
local has_signals
|
|
516
|
-
has_signals=$(jq 'has("signals")' "$json_file" 2>/dev/null)
|
|
517
|
-
|
|
518
|
-
# Generate ISO timestamp
|
|
519
|
-
local generated_at
|
|
520
|
-
generated_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
521
|
-
|
|
522
|
-
# Extract metadata from JSON
|
|
523
|
-
local version colony_id
|
|
524
|
-
version=$(jq -r '.version // "1.0.0"' "$json_file")
|
|
525
|
-
colony_id=$(jq -r '.colony_id // "unknown"' "$json_file")
|
|
526
|
-
|
|
527
|
-
# Build XML header with proper namespace
|
|
528
|
-
local xml_output
|
|
529
|
-
xml_output="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
|
530
|
-
<pheromones xmlns=\"http://aether.colony/schemas/pheromones\"
|
|
531
|
-
xmlns:ph=\"http://aether.colony/schemas/pheromones\"
|
|
532
|
-
version=\"$version\"
|
|
533
|
-
generated_at=\"$generated_at\"
|
|
534
|
-
colony_id=\"$colony_id\">"
|
|
535
|
-
|
|
536
|
-
# Add metadata section
|
|
537
|
-
local source_type source_version context
|
|
538
|
-
source_type=$(jq -r '.metadata.source.type // "system"' "$json_file" 2>/dev/null || echo "system")
|
|
539
|
-
source_version=$(jq -r ".metadata.source.version // \"$version\"" "$json_file" 2>/dev/null || echo "$version")
|
|
540
|
-
context=$(jq -r '.metadata.context // "Colony pheromone signal conversion"' "$json_file" 2>/dev/null || echo "Colony pheromone signal conversion")
|
|
541
|
-
|
|
542
|
-
xml_output="$xml_output
|
|
543
|
-
<metadata>
|
|
544
|
-
<source type=\"$source_type\" version=\"$source_version\">aether-pheromone-converter</source>
|
|
545
|
-
<context>$(echo "$context" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')</context>
|
|
546
|
-
</metadata>"
|
|
547
|
-
|
|
548
|
-
# Process signals - either from array or wrap single signal
|
|
549
|
-
local signal_count=0
|
|
550
|
-
local sig_array_length
|
|
551
|
-
sig_array_length=$(jq '.signals | length' "$json_file" 2>/dev/null || echo "0")
|
|
552
|
-
|
|
553
|
-
if [[ "$has_signals" == "true" && "$sig_array_length" -gt 0 ]]; then
|
|
554
|
-
local sig_idx=0
|
|
555
|
-
while [[ $sig_idx -lt $sig_array_length ]]; do
|
|
556
|
-
local signal
|
|
557
|
-
signal=$(jq -c ".signals[$sig_idx]" "$json_file" 2>/dev/null)
|
|
558
|
-
[[ -n "$signal" ]] || { ((sig_idx++)); continue; }
|
|
559
|
-
|
|
560
|
-
# Extract signal fields with defaults
|
|
561
|
-
local sig_id sig_type priority source created_at expires_at active
|
|
562
|
-
sig_id=$(echo "$signal" | jq -r '.id // "sig_'"$(date +%s)"'_'"$signal_count"'"')
|
|
563
|
-
sig_type=$(echo "$signal" | jq -r '.type // "FOCUS"' | tr '[:lower:]' '[:upper:]')
|
|
564
|
-
priority=$(echo "$signal" | jq -r '.priority // "normal"' | tr '[:upper:]' '[:lower:]')
|
|
565
|
-
source=$(echo "$signal" | jq -r '.source // "system"')
|
|
566
|
-
created_at=$(echo "$signal" | jq -r '.created_at // "'"$generated_at"'"')
|
|
567
|
-
expires_at=$(echo "$signal" | jq -r '.expires_at // empty')
|
|
568
|
-
active=$(echo "$signal" | jq -r '.active // true')
|
|
569
|
-
|
|
570
|
-
# Validate signal type against schema enum
|
|
571
|
-
case "$sig_type" in
|
|
572
|
-
FOCUS|REDIRECT|FEEDBACK) ;;
|
|
573
|
-
*) sig_type="FOCUS" ;;
|
|
574
|
-
esac
|
|
575
|
-
|
|
576
|
-
# Validate priority against schema enum
|
|
577
|
-
case "$priority" in
|
|
578
|
-
critical|high|normal|low) ;;
|
|
579
|
-
*) priority="normal" ;;
|
|
580
|
-
esac
|
|
581
|
-
|
|
582
|
-
# XML escape ID and source
|
|
583
|
-
local escaped_id escaped_source
|
|
584
|
-
escaped_id=$(echo "$sig_id" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
585
|
-
escaped_source=$(echo "$source" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
586
|
-
|
|
587
|
-
# Build signal element
|
|
588
|
-
xml_output="$xml_output
|
|
589
|
-
<signal id=\"$escaped_id\"
|
|
590
|
-
type=\"$sig_type\"
|
|
591
|
-
priority=\"$priority\"
|
|
592
|
-
source=\"$escaped_source\"
|
|
593
|
-
created_at=\"$created_at\""
|
|
594
|
-
|
|
595
|
-
# Add optional expires_at if present
|
|
596
|
-
if [[ -n "$expires_at" && "$expires_at" != "null" ]]; then
|
|
597
|
-
xml_output="$xml_output
|
|
598
|
-
expires_at=\"$expires_at\""
|
|
599
|
-
fi
|
|
600
|
-
|
|
601
|
-
xml_output="$xml_output
|
|
602
|
-
active=\"$active\">"
|
|
603
|
-
|
|
604
|
-
# Content section
|
|
605
|
-
local content_text content_data content_format
|
|
606
|
-
content_text=$(echo "$signal" | jq -r '.content.text // .message // ""')
|
|
607
|
-
content_format=$(echo "$signal" | jq -r '.content.data.format // "json"')
|
|
608
|
-
|
|
609
|
-
if [[ -n "$content_text" ]]; then
|
|
610
|
-
local escaped_text
|
|
611
|
-
escaped_text=$(echo "$content_text" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
|
612
|
-
xml_output="$xml_output
|
|
613
|
-
<content>
|
|
614
|
-
<text>$escaped_text</text>"
|
|
615
|
-
|
|
616
|
-
# Check for data attachment - convert JSON to XML elements
|
|
617
|
-
local has_data
|
|
618
|
-
has_data=$(echo "$signal" | jq 'has("content") and (.content | has("data"))' 2>/dev/null)
|
|
619
|
-
if [[ "$has_data" == "true" ]]; then
|
|
620
|
-
local data_xml
|
|
621
|
-
data_xml=$(echo "$signal" | jq -r '.content.data | to_entries | map("<\(.key)>\(.value | tostring | gsub("&"; "&") | gsub("<"; "<") | gsub(">"; ">"))</\(.key)>") | join("")' 2>/dev/null)
|
|
622
|
-
xml_output="$xml_output
|
|
623
|
-
<data format=\"$content_format\">$data_xml</data>"
|
|
624
|
-
fi
|
|
625
|
-
|
|
626
|
-
xml_output="$xml_output
|
|
627
|
-
</content>"
|
|
628
|
-
fi
|
|
629
|
-
|
|
630
|
-
# Tags section
|
|
631
|
-
local tags_json
|
|
632
|
-
tags_json=$(echo "$signal" | jq -c '.tags // []')
|
|
633
|
-
if [[ "$tags_json" != "[]" && -n "$tags_json" ]]; then
|
|
634
|
-
xml_output="$xml_output
|
|
635
|
-
<tags>"
|
|
636
|
-
local tag_count
|
|
637
|
-
tag_count=$(echo "$signal" | jq '.tags | length')
|
|
638
|
-
local tag_idx=0
|
|
639
|
-
while [[ $tag_idx -lt $tag_count ]]; do
|
|
640
|
-
local tag
|
|
641
|
-
tag=$(echo "$signal" | jq -c ".tags[$tag_idx]")
|
|
642
|
-
local tag_value tag_weight tag_category
|
|
643
|
-
tag_value=$(echo "$tag" | jq -r '.value // .')
|
|
644
|
-
tag_weight=$(echo "$tag" | jq -r '.weight // "1.0"')
|
|
645
|
-
tag_category=$(echo "$tag" | jq -r '.category // empty')
|
|
646
|
-
|
|
647
|
-
local escaped_tag
|
|
648
|
-
escaped_tag=$(echo "$tag_value" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
649
|
-
|
|
650
|
-
if [[ -n "$tag_category" && "$tag_category" != "null" ]]; then
|
|
651
|
-
xml_output="$xml_output
|
|
652
|
-
<tag weight=\"$tag_weight\" category=\"$tag_category\">$escaped_tag</tag>"
|
|
653
|
-
else
|
|
654
|
-
xml_output="$xml_output
|
|
655
|
-
<tag weight=\"$tag_weight\">$escaped_tag</tag>"
|
|
656
|
-
fi
|
|
657
|
-
((tag_idx++))
|
|
658
|
-
done
|
|
659
|
-
xml_output="$xml_output
|
|
660
|
-
</tags>"
|
|
661
|
-
fi
|
|
662
|
-
|
|
663
|
-
# Scope section
|
|
664
|
-
local scope_global scope_castes scope_paths
|
|
665
|
-
scope_global=$(echo "$signal" | jq -r '.scope.global // false')
|
|
666
|
-
xml_output="$xml_output
|
|
667
|
-
<scope global=\"$scope_global\">"
|
|
668
|
-
|
|
669
|
-
# Castes
|
|
670
|
-
local castes_json
|
|
671
|
-
castes_json=$(echo "$signal" | jq -c '.scope.castes // []' 2>/dev/null)
|
|
672
|
-
if [[ "$castes_json" != "[]" && -n "$castes_json" && "$castes_json" != "null" ]]; then
|
|
673
|
-
xml_output="$xml_output
|
|
674
|
-
<castes match=\"any\">"
|
|
675
|
-
local caste_count
|
|
676
|
-
caste_count=$(echo "$signal" | jq '.scope.castes | length' 2>/dev/null)
|
|
677
|
-
local caste_idx=0
|
|
678
|
-
while [[ $caste_idx -lt $caste_count ]]; do
|
|
679
|
-
local caste
|
|
680
|
-
caste=$(echo "$signal" | jq -r ".scope.castes[$caste_idx]" 2>/dev/null)
|
|
681
|
-
# Validate caste against schema enum
|
|
682
|
-
case "$caste" in
|
|
683
|
-
builder|watcher|scout|chaos|oracle|architect|prime|colonizer|route_setter|archaeologist|ambassador|auditor|chronicler|gatekeeper|guardian|includer|keeper|measurer|probe|sage|tracker|weaver)
|
|
684
|
-
xml_output="$xml_output
|
|
685
|
-
<caste>$caste</caste>"
|
|
686
|
-
;;
|
|
687
|
-
esac
|
|
688
|
-
((caste_idx++))
|
|
689
|
-
done
|
|
690
|
-
xml_output="$xml_output
|
|
691
|
-
</castes>"
|
|
692
|
-
fi
|
|
693
|
-
|
|
694
|
-
# Paths
|
|
695
|
-
local paths_json
|
|
696
|
-
paths_json=$(echo "$signal" | jq -c '.scope.paths // []' 2>/dev/null)
|
|
697
|
-
if [[ "$paths_json" != "[]" && -n "$paths_json" && "$paths_json" != "null" ]]; then
|
|
698
|
-
xml_output="$xml_output
|
|
699
|
-
<paths match=\"any\">"
|
|
700
|
-
local path_count
|
|
701
|
-
path_count=$(echo "$signal" | jq '.scope.paths | length' 2>/dev/null)
|
|
702
|
-
local path_idx=0
|
|
703
|
-
while [[ $path_idx -lt $path_count ]]; do
|
|
704
|
-
local path
|
|
705
|
-
path=$(echo "$signal" | jq -r ".scope.paths[$path_idx]" 2>/dev/null)
|
|
706
|
-
local escaped_path
|
|
707
|
-
escaped_path=$(echo "$path" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
708
|
-
xml_output="$xml_output
|
|
709
|
-
<path>$escaped_path</path>"
|
|
710
|
-
((path_idx++))
|
|
711
|
-
done
|
|
712
|
-
xml_output="$xml_output
|
|
713
|
-
</paths>"
|
|
714
|
-
fi
|
|
715
|
-
|
|
716
|
-
xml_output="$xml_output
|
|
717
|
-
</scope>"
|
|
718
|
-
|
|
719
|
-
xml_output="$xml_output
|
|
720
|
-
</signal>"
|
|
721
|
-
((signal_count++))
|
|
722
|
-
((sig_idx++))
|
|
723
|
-
done
|
|
724
|
-
elif [[ "$has_signals" != "true" ]]; then
|
|
725
|
-
# Handle single signal JSON (legacy format)
|
|
726
|
-
local signal
|
|
727
|
-
signal=$(jq -c '.' "$json_file" 2>/dev/null)
|
|
728
|
-
if [[ -n "$signal" ]]; then
|
|
729
|
-
# Extract signal fields with defaults for legacy format
|
|
730
|
-
local sig_id sig_type priority source created_at expires_at active
|
|
731
|
-
sig_id=$(echo "$signal" | jq -r '.id // "sig_'"$(date +%s)"'_0"')
|
|
732
|
-
sig_type=$(echo "$signal" | jq -r '.type // "FOCUS"' | tr '[:lower:]' '[:upper:]')
|
|
733
|
-
priority=$(echo "$signal" | jq -r '.priority // "normal"' | tr '[:upper:]' '[:lower:]')
|
|
734
|
-
source=$(echo "$signal" | jq -r '.source // "system"')
|
|
735
|
-
created_at=$(echo "$signal" | jq -r '.created_at // "'"$generated_at"'"')
|
|
736
|
-
expires_at=$(echo "$signal" | jq -r '.expires_at // empty')
|
|
737
|
-
active=$(echo "$signal" | jq -r '.active // true')
|
|
738
|
-
|
|
739
|
-
# Validate signal type against schema enum
|
|
740
|
-
case "$sig_type" in
|
|
741
|
-
FOCUS|REDIRECT|FEEDBACK) ;;
|
|
742
|
-
*) sig_type="FOCUS" ;;
|
|
743
|
-
esac
|
|
744
|
-
|
|
745
|
-
# Validate priority against schema enum
|
|
746
|
-
case "$priority" in
|
|
747
|
-
critical|high|normal|low) ;;
|
|
748
|
-
*) priority="normal" ;;
|
|
749
|
-
esac
|
|
750
|
-
|
|
751
|
-
# XML escape ID and source
|
|
752
|
-
local escaped_id escaped_source
|
|
753
|
-
escaped_id=$(echo "$sig_id" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
754
|
-
escaped_source=$(echo "$source" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
755
|
-
|
|
756
|
-
# Build signal element
|
|
757
|
-
xml_output="$xml_output
|
|
758
|
-
<signal id=\"$escaped_id\"
|
|
759
|
-
type=\"$sig_type\"
|
|
760
|
-
priority=\"$priority\"
|
|
761
|
-
source=\"$escaped_source\"
|
|
762
|
-
created_at=\"$created_at\""
|
|
763
|
-
|
|
764
|
-
# Add optional expires_at if present
|
|
765
|
-
if [[ -n "$expires_at" && "$expires_at" != "null" ]]; then
|
|
766
|
-
xml_output="$xml_output
|
|
767
|
-
expires_at=\"$expires_at\""
|
|
768
|
-
fi
|
|
769
|
-
|
|
770
|
-
xml_output="$xml_output
|
|
771
|
-
active=\"$active\">"
|
|
772
|
-
|
|
773
|
-
# Content section - support legacy "message" field
|
|
774
|
-
local content_text content_format
|
|
775
|
-
content_text=$(echo "$signal" | jq -r '.content.text // .message // ""')
|
|
776
|
-
content_format=$(echo "$signal" | jq -r '.content.data.format // "json"')
|
|
777
|
-
|
|
778
|
-
if [[ -n "$content_text" ]]; then
|
|
779
|
-
local escaped_text
|
|
780
|
-
escaped_text=$(echo "$content_text" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
|
781
|
-
xml_output="$xml_output
|
|
782
|
-
<content>
|
|
783
|
-
<text>$escaped_text</text>"
|
|
784
|
-
|
|
785
|
-
# Check for data attachment - convert JSON to XML elements
|
|
786
|
-
local has_data
|
|
787
|
-
has_data=$(echo "$signal" | jq 'has("content") and (.content | has("data"))' 2>/dev/null)
|
|
788
|
-
if [[ "$has_data" == "true" ]]; then
|
|
789
|
-
local data_xml
|
|
790
|
-
data_xml=$(echo "$signal" | jq -r '.content.data | to_entries | map("<\(.key)>\(.value | tostring | gsub("&"; "&") | gsub("<"; "<") | gsub(">"; ">"))</\(.key)>") | join("")' 2>/dev/null)
|
|
791
|
-
xml_output="$xml_output
|
|
792
|
-
<data format=\"$content_format\">$data_xml</data>"
|
|
793
|
-
fi
|
|
794
|
-
|
|
795
|
-
xml_output="$xml_output
|
|
796
|
-
</content>"
|
|
797
|
-
fi
|
|
798
|
-
|
|
799
|
-
# Tags section
|
|
800
|
-
local tags_json
|
|
801
|
-
tags_json=$(echo "$signal" | jq -c '.tags // []')
|
|
802
|
-
if [[ "$tags_json" != "[]" && -n "$tags_json" ]]; then
|
|
803
|
-
xml_output="$xml_output
|
|
804
|
-
<tags>"
|
|
805
|
-
local tag_count
|
|
806
|
-
tag_count=$(echo "$signal" | jq '.tags | length')
|
|
807
|
-
local tag_idx=0
|
|
808
|
-
while [[ $tag_idx -lt $tag_count ]]; do
|
|
809
|
-
local tag
|
|
810
|
-
tag=$(echo "$signal" | jq -c ".tags[$tag_idx]")
|
|
811
|
-
local tag_value tag_weight tag_category
|
|
812
|
-
tag_value=$(echo "$tag" | jq -r '.value // .')
|
|
813
|
-
tag_weight=$(echo "$tag" | jq -r '.weight // "1.0"')
|
|
814
|
-
tag_category=$(echo "$tag" | jq -r '.category // empty')
|
|
815
|
-
|
|
816
|
-
local escaped_tag
|
|
817
|
-
escaped_tag=$(echo "$tag_value" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
818
|
-
|
|
819
|
-
if [[ -n "$tag_category" && "$tag_category" != "null" ]]; then
|
|
820
|
-
xml_output="$xml_output
|
|
821
|
-
<tag weight=\"$tag_weight\" category=\"$tag_category\">$escaped_tag</tag>"
|
|
822
|
-
else
|
|
823
|
-
xml_output="$xml_output
|
|
824
|
-
<tag weight=\"$tag_weight\">$escaped_tag</tag>"
|
|
825
|
-
fi
|
|
826
|
-
((tag_idx++))
|
|
827
|
-
done
|
|
828
|
-
xml_output="$xml_output
|
|
829
|
-
</tags>"
|
|
830
|
-
fi
|
|
831
|
-
|
|
832
|
-
# Scope section
|
|
833
|
-
local scope_global
|
|
834
|
-
scope_global=$(echo "$signal" | jq -r '.scope.global // false')
|
|
835
|
-
xml_output="$xml_output
|
|
836
|
-
<scope global=\"$scope_global\">"
|
|
837
|
-
|
|
838
|
-
# Castes
|
|
839
|
-
local castes_json
|
|
840
|
-
castes_json=$(echo "$signal" | jq -c '.scope.castes // []' 2>/dev/null)
|
|
841
|
-
if [[ "$castes_json" != "[]" && -n "$castes_json" && "$castes_json" != "null" ]]; then
|
|
842
|
-
xml_output="$xml_output
|
|
843
|
-
<castes match=\"any\">"
|
|
844
|
-
local caste_count
|
|
845
|
-
caste_count=$(echo "$signal" | jq '.scope.castes | length' 2>/dev/null)
|
|
846
|
-
local caste_idx=0
|
|
847
|
-
while [[ $caste_idx -lt $caste_count ]]; do
|
|
848
|
-
local caste
|
|
849
|
-
caste=$(echo "$signal" | jq -r ".scope.castes[$caste_idx]" 2>/dev/null)
|
|
850
|
-
case "$caste" in
|
|
851
|
-
builder|watcher|scout|chaos|oracle|architect|prime|colonizer|route_setter|archaeologist|ambassador|auditor|chronicler|gatekeeper|guardian|includer|keeper|measurer|probe|sage|tracker|weaver)
|
|
852
|
-
xml_output="$xml_output
|
|
853
|
-
<caste>$caste</caste>"
|
|
854
|
-
;;
|
|
855
|
-
esac
|
|
856
|
-
((caste_idx++))
|
|
857
|
-
done
|
|
858
|
-
xml_output="$xml_output
|
|
859
|
-
</castes>"
|
|
860
|
-
fi
|
|
861
|
-
|
|
862
|
-
# Paths
|
|
863
|
-
local paths_json
|
|
864
|
-
paths_json=$(echo "$signal" | jq -c '.scope.paths // []' 2>/dev/null)
|
|
865
|
-
if [[ "$paths_json" != "[]" && -n "$paths_json" && "$paths_json" != "null" ]]; then
|
|
866
|
-
xml_output="$xml_output
|
|
867
|
-
<paths match=\"any\">"
|
|
868
|
-
local path_count
|
|
869
|
-
path_count=$(echo "$signal" | jq '.scope.paths | length' 2>/dev/null)
|
|
870
|
-
local path_idx=0
|
|
871
|
-
while [[ $path_idx -lt $path_count ]]; do
|
|
872
|
-
local path
|
|
873
|
-
path=$(echo "$signal" | jq -r ".scope.paths[$path_idx]" 2>/dev/null)
|
|
874
|
-
local escaped_path
|
|
875
|
-
escaped_path=$(echo "$path" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
876
|
-
xml_output="$xml_output
|
|
877
|
-
<path>$escaped_path</path>"
|
|
878
|
-
((path_idx++))
|
|
879
|
-
done
|
|
880
|
-
xml_output="$xml_output
|
|
881
|
-
</paths>"
|
|
882
|
-
fi
|
|
883
|
-
|
|
884
|
-
xml_output="$xml_output
|
|
885
|
-
</scope>"
|
|
886
|
-
|
|
887
|
-
xml_output="$xml_output
|
|
888
|
-
</signal>"
|
|
889
|
-
((signal_count++))
|
|
890
|
-
fi
|
|
891
|
-
fi
|
|
892
|
-
|
|
893
|
-
# Close root element
|
|
894
|
-
xml_output="$xml_output
|
|
895
|
-
</pheromones>"
|
|
896
|
-
|
|
897
|
-
# Write to file if output path specified
|
|
898
|
-
local output_path=""
|
|
899
|
-
if [[ -n "$output_xml" ]]; then
|
|
900
|
-
local output_dir
|
|
901
|
-
output_dir=$(dirname "$output_xml")
|
|
902
|
-
if [[ ! -d "$output_dir" ]]; then
|
|
903
|
-
mkdir -p "$output_dir" 2>/dev/null || {
|
|
904
|
-
xml_json_err "Cannot create output directory: $output_dir"
|
|
905
|
-
return 1
|
|
906
|
-
}
|
|
907
|
-
fi
|
|
908
|
-
echo "$xml_output" > "$output_xml" || {
|
|
909
|
-
xml_json_err "Failed to write output file: $output_xml"
|
|
910
|
-
return 1
|
|
911
|
-
}
|
|
912
|
-
output_path="$output_xml"
|
|
913
|
-
fi
|
|
914
|
-
|
|
915
|
-
# Validate against XSD schema if available
|
|
916
|
-
local validation_result="false"
|
|
917
|
-
if [[ -f "$xsd_file" && -n "$output_path" ]]; then
|
|
918
|
-
local validation_output
|
|
919
|
-
validation_output=$(xml-validate "$output_path" "$xsd_file" 2>/dev/null)
|
|
920
|
-
if echo "$validation_output" | jq -e '.result.valid' >/dev/null 2>&1; then
|
|
921
|
-
validation_result="true"
|
|
922
|
-
fi
|
|
923
|
-
elif [[ -f "$xsd_file" ]]; then
|
|
924
|
-
# Validate in-memory by writing to temp file
|
|
925
|
-
local temp_xml
|
|
926
|
-
temp_xml=$(mktemp)
|
|
927
|
-
echo "$xml_output" > "$temp_xml"
|
|
928
|
-
local validation_output
|
|
929
|
-
validation_output=$(xml-validate "$temp_xml" "$xsd_file" 2>/dev/null)
|
|
930
|
-
if echo "$validation_output" | jq -e '.result.valid' >/dev/null 2>&1; then
|
|
931
|
-
validation_result="true"
|
|
932
|
-
fi
|
|
933
|
-
rm -f "$temp_xml"
|
|
934
|
-
fi
|
|
935
|
-
|
|
936
|
-
# Build result JSON
|
|
937
|
-
local escaped_xml result_json
|
|
938
|
-
escaped_xml=$(echo "$xml_output" | jq -Rs '.')
|
|
939
|
-
result_json="{\"xml\":$escaped_xml,\"validated\":$validation_result,\"signals\":$signal_count"
|
|
940
|
-
if [[ -n "$output_path" ]]; then
|
|
941
|
-
local escaped_path
|
|
942
|
-
escaped_path=$(echo "$output_path" | jq -Rs '.[:-1]')
|
|
943
|
-
result_json="$result_json,\"path\":$escaped_path"
|
|
944
|
-
fi
|
|
945
|
-
result_json="$result_json}"
|
|
946
|
-
|
|
947
|
-
xml_json_ok "$result_json"
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
# pheromone-from-xml: Parse pheromone XML to JSON
|
|
951
|
-
# Usage: pheromone-from-xml <pheromone_xml_file>
|
|
952
|
-
# Returns: {"ok":true,"result":{"signal":"focus",...}}
|
|
953
|
-
pheromone-from-xml() {
|
|
954
|
-
local xml_file="${1:-}"
|
|
955
|
-
|
|
956
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
957
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
958
|
-
|
|
959
|
-
if [[ "$XMLSTARLET_AVAILABLE" != "true" ]]; then
|
|
960
|
-
xml_json_err "xmlstarlet required for pheromone parsing"
|
|
961
|
-
return 1
|
|
962
|
-
fi
|
|
963
|
-
|
|
964
|
-
# Extract pheromone fields from XML
|
|
965
|
-
local signal priority message timestamp source context
|
|
966
|
-
signal=$(xmlstarlet sel -t -v "/pheromone/signal" "$xml_file" 2>/dev/null || echo "")
|
|
967
|
-
priority=$(xmlstarlet sel -t -v "/pheromone/priority" "$xml_file" 2>/dev/null || echo "normal")
|
|
968
|
-
message=$(xmlstarlet sel -t -v "/pheromone/message" "$xml_file" 2>/dev/null || echo "")
|
|
969
|
-
timestamp=$(xmlstarlet sel -t -v "/pheromone/timestamp" "$xml_file" 2>/dev/null || echo "")
|
|
970
|
-
source=$(xmlstarlet sel -t -v "/pheromone/source" "$xml_file" 2>/dev/null || echo "colony")
|
|
971
|
-
context=$(xmlstarlet sel -t -v "/pheromone/context" "$xml_file" 2>/dev/null || echo "")
|
|
972
|
-
|
|
973
|
-
# Build JSON result
|
|
974
|
-
local json_result
|
|
975
|
-
json_result=$(jq -n \
|
|
976
|
-
--arg signal "$signal" \
|
|
977
|
-
--arg priority "$priority" \
|
|
978
|
-
--arg message "$message" \
|
|
979
|
-
--arg timestamp "$timestamp" \
|
|
980
|
-
--arg source "$source" \
|
|
981
|
-
--arg context "$context" \
|
|
982
|
-
'{signal: $signal, priority: $priority, message: $message, timestamp: $timestamp, source: $source, context: (if $context == "" then null else $context end)}')
|
|
983
|
-
|
|
984
|
-
xml_json_ok "$json_result"
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
# ============================================================================
|
|
988
|
-
# Queen-Wisdom XML Format
|
|
989
|
-
# ============================================================================
|
|
990
|
-
|
|
991
|
-
# queen-wisdom-to-xml: Convert queen wisdom JSON to XML
|
|
992
|
-
# Usage: queen-wisdom-to-xml <wisdom_json_file>
|
|
993
|
-
# Returns: {"ok":true,"result":{"xml":"<queen-wisdom>...</queen-wisdom>"}}
|
|
994
|
-
queen-wisdom-to-xml() {
|
|
995
|
-
local json_file="${1:-}"
|
|
996
|
-
|
|
997
|
-
[[ -z "$json_file" ]] && { xml_json_err "Missing JSON file argument"; return 1; }
|
|
998
|
-
[[ -f "$json_file" ]] || { xml_json_err "JSON file not found: $json_file"; return 1; }
|
|
999
|
-
|
|
1000
|
-
if ! jq empty "$json_file" 2>/dev/null; then
|
|
1001
|
-
xml_json_err "Invalid JSON file: $json_file"
|
|
1002
|
-
return 1
|
|
1003
|
-
fi
|
|
1004
|
-
|
|
1005
|
-
# Convert queen wisdom to structured XML
|
|
1006
|
-
local xml_output
|
|
1007
|
-
xml_output=$(jq -r '
|
|
1008
|
-
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
|
1009
|
-
"<queen-wisdom version=\"1.0\">\n" +
|
|
1010
|
-
" <directive>" + (.directive // "") + "</directive>\n" +
|
|
1011
|
-
(if .patterns then
|
|
1012
|
-
" <patterns>\n" +
|
|
1013
|
-
(.patterns | map(" <pattern>\(.)</pattern>") | join("\n")) +
|
|
1014
|
-
"\n </patterns>\n"
|
|
1015
|
-
else "" end) +
|
|
1016
|
-
(if .constraints then
|
|
1017
|
-
" <constraints>\n" +
|
|
1018
|
-
(.constraints | map(" <constraint>\(.)</constraint>") | join("\n")) +
|
|
1019
|
-
"\n </constraints>\n"
|
|
1020
|
-
else "" end) +
|
|
1021
|
-
" <timestamp>" + (.timestamp // (now | todateiso8601)) + "</timestamp>\n" +
|
|
1022
|
-
"</queen-wisdom>"
|
|
1023
|
-
' "$json_file" 2>/dev/null) || {
|
|
1024
|
-
xml_json_err "Queen wisdom conversion failed"
|
|
1025
|
-
return 1
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
local escaped_xml
|
|
1029
|
-
escaped_xml=$(echo "$xml_output" | jq -Rs '.')
|
|
1030
|
-
xml_json_ok "{\"xml\":$escaped_xml}"
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
# queen-wisdom-from-xml: Parse queen wisdom XML to JSON
|
|
1034
|
-
# Usage: queen-wisdom-from-xml <wisdom_xml_file>
|
|
1035
|
-
# Returns: {"ok":true,"result":{"directive":"...",...}}
|
|
1036
|
-
queen-wisdom-from-xml() {
|
|
1037
|
-
local xml_file="${1:-}"
|
|
1038
|
-
|
|
1039
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
1040
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
1041
|
-
|
|
1042
|
-
if [[ "$XMLSTARLET_AVAILABLE" != "true" ]]; then
|
|
1043
|
-
xml_json_err "xmlstarlet required for queen wisdom parsing"
|
|
1044
|
-
return 1
|
|
1045
|
-
fi
|
|
1046
|
-
|
|
1047
|
-
# Extract fields
|
|
1048
|
-
local directive timestamp
|
|
1049
|
-
directive=$(xmlstarlet sel -t -v "/queen-wisdom/directive" "$xml_file" 2>/dev/null || echo "")
|
|
1050
|
-
timestamp=$(xmlstarlet sel -t -v "/queen-wisdom/timestamp" "$xml_file" 2>/dev/null || echo "")
|
|
1051
|
-
|
|
1052
|
-
# Extract arrays
|
|
1053
|
-
local patterns_json constraints_json
|
|
1054
|
-
patterns_json=$(xmlstarlet sel -t -m "/queen-wisdom/patterns/pattern" -v "." -n "$xml_file" 2>/dev/null | jq -R -s 'split("\n") | map(select(length > 0))')
|
|
1055
|
-
constraints_json=$(xmlstarlet sel -t -m "/queen-wisdom/constraints/constraint" -v "." -n "$xml_file" 2>/dev/null | jq -R -s 'split("\n") | map(select(length > 0))')
|
|
1056
|
-
|
|
1057
|
-
# Build result
|
|
1058
|
-
local json_result
|
|
1059
|
-
json_result=$(jq -n \
|
|
1060
|
-
--arg directive "$directive" \
|
|
1061
|
-
--arg timestamp "$timestamp" \
|
|
1062
|
-
--argjson patterns "$patterns_json" \
|
|
1063
|
-
--argjson constraints "$constraints_json" \
|
|
1064
|
-
'{directive: $directive, timestamp: $timestamp, patterns: $patterns, constraints: $constraints}')
|
|
1065
|
-
|
|
1066
|
-
xml_json_ok "$json_result"
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
# ============================================================================
|
|
1070
|
-
# Multi-Colony Registry XML
|
|
1071
|
-
# ============================================================================
|
|
1072
|
-
|
|
1073
|
-
# registry-to-xml: Convert colony registry JSON to XML
|
|
1074
|
-
# Usage: registry-to-xml <registry_json_file>
|
|
1075
|
-
# Returns: {"ok":true,"result":{"xml":"<colony-registry>...</colony-registry>"}}
|
|
1076
|
-
registry-to-xml() {
|
|
1077
|
-
local json_file="${1:-}"
|
|
1078
|
-
|
|
1079
|
-
[[ -z "$json_file" ]] && { xml_json_err "Missing JSON file argument"; return 1; }
|
|
1080
|
-
[[ -f "$json_file" ]] || { xml_json_err "JSON file not found: $json_file"; return 1; }
|
|
1081
|
-
|
|
1082
|
-
if ! jq empty "$json_file" 2>/dev/null; then
|
|
1083
|
-
xml_json_err "Invalid JSON file: $json_file"
|
|
1084
|
-
return 1
|
|
1085
|
-
fi
|
|
1086
|
-
|
|
1087
|
-
# Convert registry to XML
|
|
1088
|
-
local xml_output
|
|
1089
|
-
xml_output=$(jq -r '
|
|
1090
|
-
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
|
|
1091
|
-
"<colony-registry version=\"1.0\" generated=\"" + (now | todateiso8601) + "\">\n" +
|
|
1092
|
-
(if .colonies then
|
|
1093
|
-
(.colonies | map(
|
|
1094
|
-
" <colony id=\"" + .id + "\">\n" +
|
|
1095
|
-
" <name>" + (.name // "") + "</name>\n" +
|
|
1096
|
-
" <status>" + (.status // "unknown") + "</status>\n" +
|
|
1097
|
-
" <location>" + (.location // "") + "</location>\n" +
|
|
1098
|
-
(if .pheromones then
|
|
1099
|
-
" <pheromones>\n" +
|
|
1100
|
-
(.pheromones | map(
|
|
1101
|
-
" <pheromone signal=\"" + .signal + "\">" + (.message // "") + "</pheromone>"
|
|
1102
|
-
) | join("\n")) +
|
|
1103
|
-
"\n </pheromones>\n"
|
|
1104
|
-
else "" end) +
|
|
1105
|
-
" </colony>"
|
|
1106
|
-
) | join("\n")) + "\n"
|
|
1107
|
-
else "" end) +
|
|
1108
|
-
"</colony-registry>"
|
|
1109
|
-
' "$json_file" 2>/dev/null) || {
|
|
1110
|
-
xml_json_err "Registry conversion failed"
|
|
1111
|
-
return 1
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
local escaped_xml
|
|
1115
|
-
escaped_xml=$(echo "$xml_output" | jq -Rs '.')
|
|
1116
|
-
xml_json_ok "{\"xml\":$escaped_xml}"
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
# registry-from-xml: Parse colony registry XML to JSON
|
|
1120
|
-
# Usage: registry-from-xml <registry_xml_file>
|
|
1121
|
-
# Returns: {"ok":true,"result":{"colonies":[...]}}
|
|
1122
|
-
registry-from-xml() {
|
|
1123
|
-
local xml_file="${1:-}"
|
|
1124
|
-
|
|
1125
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
1126
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
1127
|
-
|
|
1128
|
-
if [[ "$XMLSTARLET_AVAILABLE" != "true" ]]; then
|
|
1129
|
-
xml_json_err "xmlstarlet required for registry parsing"
|
|
1130
|
-
return 1
|
|
1131
|
-
fi
|
|
1132
|
-
|
|
1133
|
-
# Extract colonies
|
|
1134
|
-
local colonies_json
|
|
1135
|
-
colonies_json=$(xmlstarlet sel -t -m "/colony-registry/colony" \
|
|
1136
|
-
-v "@id" -o '|' \
|
|
1137
|
-
-v "name" -o '|' \
|
|
1138
|
-
-v "status" -o '|' \
|
|
1139
|
-
-v "location" -n \
|
|
1140
|
-
"$xml_file" 2>/dev/null | \
|
|
1141
|
-
awk -F'|' 'NF>=3 {
|
|
1142
|
-
printf "{\"id\":\"%s\",\"name\":\"%s\",\"status\":\"%s\",\"location\":\"%s\"}", $1, $2, $3, $4
|
|
1143
|
-
}' | \
|
|
1144
|
-
jq -s '.')
|
|
1145
|
-
|
|
1146
|
-
local json_result
|
|
1147
|
-
json_result=$(jq -n --argjson colonies "$colonies_json" '{colonies: $colonies}')
|
|
1148
|
-
|
|
1149
|
-
xml_json_ok "$json_result"
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
# ============================================================================
|
|
1153
|
-
# Pheromone Export to Eternal Memory
|
|
1154
|
-
# ============================================================================
|
|
1155
|
-
|
|
1156
|
-
# pheromone-export: Export pheromones to eternal XML format
|
|
1157
|
-
# Usage: pheromone-export [input_json] [output_xml] [session_id]
|
|
1158
|
-
# input_json: Path to pheromones.json (default: .aether/data/pheromones.json)
|
|
1159
|
-
# output_xml: Path to output XML (default: ~/.aether/eternal/pheromones.xml)
|
|
1160
|
-
# session_id: Colony session ID for namespace generation (optional, auto-detected from JSON)
|
|
1161
|
-
# Returns: {"ok":true,"result":{"exported":true,"path":"...","signals":N,"namespace":"..."}} or error
|
|
1162
|
-
pheromone-export() {
|
|
1163
|
-
local input_json="${1:-.aether/data/pheromones.json}"
|
|
1164
|
-
local output_xml="${2:-$HOME/.aether/eternal/pheromones.xml}"
|
|
1165
|
-
local session_id="${3:-}"
|
|
1166
|
-
local schema_file="${4:-.aether/schemas/pheromone.xsd}"
|
|
1167
|
-
|
|
1168
|
-
# Validate input file exists
|
|
1169
|
-
[[ -f "$input_json" ]] || { xml_json_err "Pheromone JSON file not found: $input_json"; return 1; }
|
|
1170
|
-
|
|
1171
|
-
# Validate JSON
|
|
1172
|
-
if ! jq empty "$input_json" 2>/dev/null; then
|
|
1173
|
-
xml_json_err "Invalid JSON in pheromone file: $input_json"
|
|
1174
|
-
return 1
|
|
1175
|
-
fi
|
|
1176
|
-
|
|
1177
|
-
# Get absolute paths for schema validation
|
|
1178
|
-
local abs_schema
|
|
1179
|
-
abs_schema="$(cd "$(dirname "$schema_file")" && pwd)/$(basename "$schema_file")" 2>/dev/null || abs_schema="$schema_file"
|
|
1180
|
-
|
|
1181
|
-
# Generate ISO timestamp for XML
|
|
1182
|
-
local generated_at
|
|
1183
|
-
generated_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1184
|
-
|
|
1185
|
-
# Get version and colony_id from JSON
|
|
1186
|
-
local version colony_id
|
|
1187
|
-
version=$(jq -r '.version // "1.0.0"' "$input_json")
|
|
1188
|
-
colony_id=$(jq -r '.colony_id // "unknown"' "$input_json")
|
|
1189
|
-
|
|
1190
|
-
# Auto-detect session_id from JSON if not provided
|
|
1191
|
-
if [[ -z "$session_id" ]]; then
|
|
1192
|
-
session_id=$(jq -r '.session_id // .colony_id // ""' "$input_json")
|
|
1193
|
-
fi
|
|
1194
|
-
|
|
1195
|
-
# Generate colony namespace if session_id available
|
|
1196
|
-
local colony_namespace=""
|
|
1197
|
-
local colony_prefix=""
|
|
1198
|
-
if [[ -n "$session_id" ]]; then
|
|
1199
|
-
local ns_result
|
|
1200
|
-
ns_result=$(generate-colony-namespace "$session_id" 2>/dev/null)
|
|
1201
|
-
if echo "$ns_result" | jq -e '.ok' >/dev/null 2>&1; then
|
|
1202
|
-
colony_namespace=$(echo "$ns_result" | jq -r '.result.namespace')
|
|
1203
|
-
colony_prefix=$(echo "$ns_result" | jq -r '.result.prefix')
|
|
1204
|
-
fi
|
|
1205
|
-
fi
|
|
1206
|
-
|
|
1207
|
-
# Build XML header with proper namespace
|
|
1208
|
-
local xml_output
|
|
1209
|
-
xml_output="<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
|
1210
|
-
<pheromones xmlns=\"http://aether.colony/schemas/pheromones\"
|
|
1211
|
-
xmlns:ph=\"http://aether.colony/schemas/pheromones\""
|
|
1212
|
-
|
|
1213
|
-
# Add colony namespace if available
|
|
1214
|
-
if [[ -n "$colony_namespace" ]]; then
|
|
1215
|
-
xml_output="$xml_output
|
|
1216
|
-
xmlns:col=\"$colony_namespace\"
|
|
1217
|
-
col:session=\"$session_id\"
|
|
1218
|
-
col:prefix=\"$colony_prefix\""
|
|
1219
|
-
fi
|
|
1220
|
-
|
|
1221
|
-
xml_output="$xml_output
|
|
1222
|
-
version=\"$version\"
|
|
1223
|
-
generated_at=\"$generated_at\"
|
|
1224
|
-
colony_id=\"$colony_id\">
|
|
1225
|
-
<metadata>
|
|
1226
|
-
<source type=\"system\" version=\"$version\">aether-pheromone-export</source>
|
|
1227
|
-
<context>Colony pheromone trail export to eternal memory</context>
|
|
1228
|
-
</metadata>"
|
|
1229
|
-
|
|
1230
|
-
# Process each signal
|
|
1231
|
-
local signal_count=0
|
|
1232
|
-
local signals_json
|
|
1233
|
-
signals_json=$(jq -c '.signals // [] | .[]' "$input_json" 2>/dev/null)
|
|
1234
|
-
|
|
1235
|
-
if [[ -n "$signals_json" ]]; then
|
|
1236
|
-
while IFS= read -r signal; do
|
|
1237
|
-
[[ -n "$signal" ]] || continue
|
|
1238
|
-
|
|
1239
|
-
local sig_id sig_type priority source created_at expires_at active
|
|
1240
|
-
sig_id=$(echo "$signal" | jq -r '.id // "unknown"')
|
|
1241
|
-
sig_type=$(echo "$signal" | jq -r '.type // "FOCUS"')
|
|
1242
|
-
priority=$(echo "$signal" | jq -r '.priority // "normal"')
|
|
1243
|
-
source=$(echo "$signal" | jq -r '.source // "system"')
|
|
1244
|
-
created_at=$(echo "$signal" | jq -r '.created_at // ""')
|
|
1245
|
-
expires_at=$(echo "$signal" | jq -r '.expires_at // ""')
|
|
1246
|
-
active=$(echo "$signal" | jq -r '.active // true')
|
|
1247
|
-
|
|
1248
|
-
# XML escape the ID and source
|
|
1249
|
-
local escaped_id escaped_source
|
|
1250
|
-
escaped_id=$(echo "$sig_id" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
1251
|
-
escaped_source=$(echo "$source" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
1252
|
-
|
|
1253
|
-
xml_output="$xml_output
|
|
1254
|
-
<signal id=\"$escaped_id\"
|
|
1255
|
-
type=\"$sig_type\"
|
|
1256
|
-
priority=\"$priority\"
|
|
1257
|
-
source=\"$escaped_source\"
|
|
1258
|
-
created_at=\"$created_at\""
|
|
1259
|
-
|
|
1260
|
-
if [[ -n "$expires_at" && "$expires_at" != "null" ]]; then
|
|
1261
|
-
xml_output="$xml_output
|
|
1262
|
-
expires_at=\"$expires_at\""
|
|
1263
|
-
fi
|
|
1264
|
-
|
|
1265
|
-
xml_output="$xml_output
|
|
1266
|
-
active=\"$active\">"
|
|
1267
|
-
|
|
1268
|
-
# Content section
|
|
1269
|
-
local content_text
|
|
1270
|
-
content_text=$(echo "$signal" | jq -r '.content.text // ""')
|
|
1271
|
-
if [[ -n "$content_text" ]]; then
|
|
1272
|
-
local escaped_text
|
|
1273
|
-
escaped_text=$(echo "$content_text" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
|
|
1274
|
-
xml_output="$xml_output
|
|
1275
|
-
<content>
|
|
1276
|
-
<text>$escaped_text</text>"
|
|
1277
|
-
|
|
1278
|
-
# Check for data attachment
|
|
1279
|
-
local has_data data_format
|
|
1280
|
-
has_data=$(echo "$signal" | jq -r 'has("content") and has("content.data")')
|
|
1281
|
-
if [[ "$has_data" == "true" ]]; then
|
|
1282
|
-
data_format=$(echo "$signal" | jq -r '.content.data.format // "json"')
|
|
1283
|
-
xml_output="$xml_output
|
|
1284
|
-
<data format=\"$data_format\">"
|
|
1285
|
-
# Add data content as CDATA or escaped
|
|
1286
|
-
local data_content
|
|
1287
|
-
data_content=$(echo "$signal" | jq -c '.content.data' 2>/dev/null)
|
|
1288
|
-
xml_output="$xml_output$data_content"
|
|
1289
|
-
xml_output="$xml_output</data>"
|
|
1290
|
-
fi
|
|
1291
|
-
|
|
1292
|
-
xml_output="$xml_output
|
|
1293
|
-
</content>"
|
|
1294
|
-
fi
|
|
1295
|
-
|
|
1296
|
-
# Tags section
|
|
1297
|
-
local tags_json
|
|
1298
|
-
tags_json=$(echo "$signal" | jq -c '.tags // []')
|
|
1299
|
-
if [[ "$tags_json" != "[]" && -n "$tags_json" ]]; then
|
|
1300
|
-
xml_output="$xml_output
|
|
1301
|
-
<tags>"
|
|
1302
|
-
local tags_array
|
|
1303
|
-
tags_array=$(echo "$signal" | jq -c '.tags // [] | .[]')
|
|
1304
|
-
while IFS= read -r tag; do
|
|
1305
|
-
[[ -n "$tag" ]] || continue
|
|
1306
|
-
local tag_value tag_weight tag_category
|
|
1307
|
-
tag_value=$(echo "$tag" | jq -r '.value // .')
|
|
1308
|
-
tag_weight=$(echo "$tag" | jq -r '.weight // "1.0"')
|
|
1309
|
-
tag_category=$(echo "$tag" | jq -r '.category // ""')
|
|
1310
|
-
|
|
1311
|
-
local escaped_tag
|
|
1312
|
-
escaped_tag=$(echo "$tag_value" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
1313
|
-
|
|
1314
|
-
if [[ -n "$tag_category" && "$tag_category" != "null" ]]; then
|
|
1315
|
-
xml_output="$xml_output
|
|
1316
|
-
<tag weight=\"$tag_weight\" category=\"$tag_category\">$escaped_tag</tag>"
|
|
1317
|
-
else
|
|
1318
|
-
xml_output="$xml_output
|
|
1319
|
-
<tag weight=\"$tag_weight\">$escaped_tag</tag>"
|
|
1320
|
-
fi
|
|
1321
|
-
done <<< "$tags_array"
|
|
1322
|
-
xml_output="$xml_output
|
|
1323
|
-
</tags>"
|
|
1324
|
-
fi
|
|
1325
|
-
|
|
1326
|
-
# Scope section
|
|
1327
|
-
local scope_global
|
|
1328
|
-
scope_global=$(echo "$signal" | jq -r '.scope.global // false')
|
|
1329
|
-
xml_output="$xml_output
|
|
1330
|
-
<scope global=\"$scope_global\">"
|
|
1331
|
-
|
|
1332
|
-
# Castes
|
|
1333
|
-
local castes_json
|
|
1334
|
-
castes_json=$(echo "$signal" | jq -c '.scope.castes // []')
|
|
1335
|
-
if [[ "$castes_json" != "[]" && -n "$castes_json" ]]; then
|
|
1336
|
-
xml_output="$xml_output
|
|
1337
|
-
<castes match=\"any\">"
|
|
1338
|
-
local caste_array
|
|
1339
|
-
caste_array=$(echo "$signal" | jq -r '.scope.castes[]')
|
|
1340
|
-
while IFS= read -r caste; do
|
|
1341
|
-
[[ -n "$caste" ]] || continue
|
|
1342
|
-
xml_output="$xml_output
|
|
1343
|
-
<caste>$caste</caste>"
|
|
1344
|
-
done <<< "$caste_array"
|
|
1345
|
-
xml_output="$xml_output
|
|
1346
|
-
</castes>"
|
|
1347
|
-
fi
|
|
1348
|
-
|
|
1349
|
-
# Paths
|
|
1350
|
-
local paths_json
|
|
1351
|
-
paths_json=$(echo "$signal" | jq -c '.scope.paths // []')
|
|
1352
|
-
if [[ "$paths_json" != "[]" && -n "$paths_json" ]]; then
|
|
1353
|
-
xml_output="$xml_output
|
|
1354
|
-
<paths match=\"any\">"
|
|
1355
|
-
local path_array
|
|
1356
|
-
path_array=$(echo "$signal" | jq -r '.scope.paths[]')
|
|
1357
|
-
while IFS= read -r path; do
|
|
1358
|
-
[[ -n "$path" ]] || continue
|
|
1359
|
-
local escaped_path
|
|
1360
|
-
escaped_path=$(echo "$path" | sed 's/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/g')
|
|
1361
|
-
xml_output="$xml_output
|
|
1362
|
-
<path>$escaped_path</path>"
|
|
1363
|
-
done <<< "$path_array"
|
|
1364
|
-
xml_output="$xml_output
|
|
1365
|
-
</paths>"
|
|
1366
|
-
fi
|
|
1367
|
-
|
|
1368
|
-
xml_output="$xml_output
|
|
1369
|
-
</scope>"
|
|
1370
|
-
|
|
1371
|
-
xml_output="$xml_output
|
|
1372
|
-
</signal>"
|
|
1373
|
-
((signal_count++))
|
|
1374
|
-
done <<< "$signals_json"
|
|
1375
|
-
fi
|
|
1376
|
-
|
|
1377
|
-
# Close root element
|
|
1378
|
-
xml_output="$xml_output
|
|
1379
|
-
</pheromones>"
|
|
1380
|
-
|
|
1381
|
-
# Ensure output directory exists
|
|
1382
|
-
local output_dir
|
|
1383
|
-
output_dir=$(dirname "$output_xml")
|
|
1384
|
-
if [[ ! -d "$output_dir" ]]; then
|
|
1385
|
-
mkdir -p "$output_dir" 2>/dev/null || {
|
|
1386
|
-
xml_json_err "Cannot create output directory: $output_dir"
|
|
1387
|
-
return 1
|
|
1388
|
-
}
|
|
1389
|
-
fi
|
|
1390
|
-
|
|
1391
|
-
# Write XML to file
|
|
1392
|
-
echo "$xml_output" > "$output_xml" || {
|
|
1393
|
-
xml_json_err "Failed to write output file: $output_xml"
|
|
1394
|
-
return 1
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
# Validate against schema if available
|
|
1398
|
-
local validation_result="false"
|
|
1399
|
-
if [[ -f "$abs_schema" ]]; then
|
|
1400
|
-
validation_result=$(xml-validate "$output_xml" "$abs_schema" 2>/dev/null)
|
|
1401
|
-
if ! echo "$validation_result" | jq -e '.result.valid' >/dev/null 2>&1; then
|
|
1402
|
-
xml_json_err "XML validation failed against schema: $abs_schema"
|
|
1403
|
-
return 1
|
|
1404
|
-
fi
|
|
1405
|
-
validation_result="true"
|
|
1406
|
-
fi
|
|
1407
|
-
|
|
1408
|
-
# Return success with metadata
|
|
1409
|
-
local escaped_output
|
|
1410
|
-
escaped_output=$(echo "$output_xml" | jq -Rs '.[:-1]')
|
|
1411
|
-
local result_json
|
|
1412
|
-
result_json="{\"exported\":true,\"path\":$escaped_output,\"signals\":$signal_count,\"validated\":$validation_result"
|
|
1413
|
-
if [[ -n "$colony_namespace" ]]; then
|
|
1414
|
-
result_json="$result_json,\"namespace\":\"$colony_namespace\",\"prefix\":\"$colony_prefix\""
|
|
1415
|
-
fi
|
|
1416
|
-
result_json="$result_json}"
|
|
1417
|
-
xml_json_ok "$result_json"
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
# ============================================================================
|
|
1421
|
-
# Colony Namespace Generation
|
|
1422
|
-
# ============================================================================
|
|
1423
|
-
|
|
1424
|
-
# Colony namespace base URI
|
|
1425
|
-
readonly COLONY_NAMESPACE_BASE="http://aether.dev/colony"
|
|
1426
|
-
|
|
1427
|
-
# generate-colony-namespace: Generate unique namespace URI for a colony session
|
|
1428
|
-
# Usage: generate-colony-namespace <session_id>
|
|
1429
|
-
# Returns: {"ok":true,"result":{"namespace":"http://aether.dev/colony/{session_id}","prefix":"col_{hash}"}}
|
|
1430
|
-
generate-colony-namespace() {
|
|
1431
|
-
local session_id="${1:-}"
|
|
1432
|
-
|
|
1433
|
-
[[ -z "$session_id" ]] && { xml_json_err "Missing session_id argument"; return 1; }
|
|
1434
|
-
|
|
1435
|
-
# Generate namespace URI
|
|
1436
|
-
local namespace_uri="${COLONY_NAMESPACE_BASE}/${session_id}"
|
|
1437
|
-
|
|
1438
|
-
# Generate short prefix from session_id (first 8 chars of MD5 hash)
|
|
1439
|
-
local prefix
|
|
1440
|
-
if command -v md5sum >/dev/null 2>&1; then
|
|
1441
|
-
prefix="col_$(echo -n "$session_id" | md5sum | cut -c1-8)"
|
|
1442
|
-
elif command -v md5 >/dev/null 2>&1; then
|
|
1443
|
-
prefix="col_$(echo -n "$session_id" | md5 | cut -c1-8)"
|
|
1444
|
-
else
|
|
1445
|
-
# Fallback: use first 8 alphanumeric chars of session_id
|
|
1446
|
-
prefix="col_$(echo -n "$session_id" | tr -cd '[:alnum:]' | cut -c1-8)"
|
|
1447
|
-
fi
|
|
1448
|
-
|
|
1449
|
-
xml_json_ok "{\"namespace\":\"$namespace_uri\",\"prefix\":\"$prefix\",\"session_id\":\"$session_id\"}"
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
# generate-cross-colony-prefix: Generate prefix for external colony pheromones
|
|
1453
|
-
# Usage: generate-cross-colony-prefix <external_session_id> [local_session_id]
|
|
1454
|
-
# Returns: {"ok":true,"result":{"prefix":"ext_{hash}_{short_id}","full_prefix":"{local_prefix}_{external_prefix}"}}
|
|
1455
|
-
generate-cross-colony-prefix() {
|
|
1456
|
-
local external_session_id="${1:-}"
|
|
1457
|
-
local local_session_id="${2:-}"
|
|
1458
|
-
|
|
1459
|
-
[[ -z "$external_session_id" ]] && { xml_json_err "Missing external_session_id argument"; return 1; }
|
|
1460
|
-
|
|
1461
|
-
# Generate external colony prefix
|
|
1462
|
-
local external_prefix
|
|
1463
|
-
if command -v md5sum >/dev/null 2>&1; then
|
|
1464
|
-
external_prefix="ext_$(echo -n "$external_session_id" | md5sum | cut -c1-6)"
|
|
1465
|
-
elif command -v md5 >/dev/null 2>&1; then
|
|
1466
|
-
external_prefix="ext_$(echo -n "$external_session_id" | md5 | cut -c1-6)"
|
|
1467
|
-
else
|
|
1468
|
-
external_prefix="ext_$(echo -n "$external_session_id" | tr -cd '[:alnum:]' | cut -c1-6)"
|
|
1469
|
-
fi
|
|
1470
|
-
|
|
1471
|
-
# If local session provided, create combined prefix for collision prevention
|
|
1472
|
-
local full_prefix="$external_prefix"
|
|
1473
|
-
if [[ -n "$local_session_id" ]]; then
|
|
1474
|
-
local local_hash
|
|
1475
|
-
if command -v md5sum >/dev/null 2>&1; then
|
|
1476
|
-
local_hash="$(echo -n "$local_session_id" | md5sum | cut -c1-4)"
|
|
1477
|
-
elif command -v md5 >/dev/null 2>&1; then
|
|
1478
|
-
local_hash="$(echo -n "$local_session_id" | md5 | cut -c1-4)"
|
|
1479
|
-
else
|
|
1480
|
-
local_hash="$(echo -n "$local_session_id" | tr -cd '[:alnum:]' | cut -c1-4)"
|
|
1481
|
-
fi
|
|
1482
|
-
full_prefix="${local_hash}_${external_prefix}"
|
|
1483
|
-
fi
|
|
1484
|
-
|
|
1485
|
-
xml_json_ok "{\"prefix\":\"$external_prefix\",\"full_prefix\":\"$full_prefix\",\"external_session\":\"$external_session_id\"}"
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
# prefix-pheromone-id: Prefix a pheromone ID to prevent collisions
|
|
1489
|
-
# Usage: prefix-pheromone-id <pheromone_id> <colony_prefix>
|
|
1490
|
-
# Returns: {"ok":true,"result":"{prefix}_{pheromone_id}"}
|
|
1491
|
-
prefix-pheromone-id() {
|
|
1492
|
-
local pheromone_id="${1:-}"
|
|
1493
|
-
local colony_prefix="${2:-}"
|
|
1494
|
-
|
|
1495
|
-
[[ -z "$pheromone_id" ]] && { xml_json_err "Missing pheromone_id argument"; return 1; }
|
|
1496
|
-
[[ -z "$colony_prefix" ]] && { xml_json_err "Missing colony_prefix argument"; return 1; }
|
|
1497
|
-
|
|
1498
|
-
# Check if already prefixed with this colony
|
|
1499
|
-
if [[ "$pheromone_id" == ${colony_prefix}_* ]]; then
|
|
1500
|
-
xml_json_ok "\"$pheromone_id\""
|
|
1501
|
-
return 0
|
|
1502
|
-
fi
|
|
1503
|
-
|
|
1504
|
-
local prefixed_id="${colony_prefix}_${pheromone_id}"
|
|
1505
|
-
xml_json_ok "\"$prefixed_id\""
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
# extract-session-from-namespace: Extract session ID from namespace URI
|
|
1509
|
-
# Usage: extract-session-from-namespace <namespace_uri>
|
|
1510
|
-
# Returns: {"ok":true,"result":"{session_id}"}
|
|
1511
|
-
extract-session-from-namespace() {
|
|
1512
|
-
local namespace_uri="${1:-}"
|
|
1513
|
-
|
|
1514
|
-
[[ -z "$namespace_uri" ]] && { xml_json_err "Missing namespace_uri argument"; return 1; }
|
|
1515
|
-
|
|
1516
|
-
# Extract session ID from http://aether.dev/colony/{session_id}
|
|
1517
|
-
local session_id
|
|
1518
|
-
if [[ "$namespace_uri" =~ ^http://aether\.dev/colony/(.+)$ ]]; then
|
|
1519
|
-
session_id="${BASH_REMATCH[1]}"
|
|
1520
|
-
xml_json_ok "\"$session_id\""
|
|
1521
|
-
else
|
|
1522
|
-
xml_json_err "Invalid colony namespace format: $namespace_uri"
|
|
1523
|
-
return 1
|
|
1524
|
-
fi
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
# validate-colony-namespace: Validate a colony namespace URI
|
|
1528
|
-
# Usage: validate-colony-namespace <namespace_uri>
|
|
1529
|
-
# Returns: {"ok":true,"result":{"valid":true,"type":"colony","session_id":"..."}}
|
|
1530
|
-
validate-colony-namespace() {
|
|
1531
|
-
local namespace_uri="${1:-}"
|
|
1532
|
-
|
|
1533
|
-
[[ -z "$namespace_uri" ]] && { xml_json_err "Missing namespace_uri argument"; return 1; }
|
|
1534
|
-
|
|
1535
|
-
# Check if it matches colony namespace pattern
|
|
1536
|
-
if [[ "$namespace_uri" =~ ^http://aether\.dev/colony/([a-zA-Z0-9_-]+)$ ]]; then
|
|
1537
|
-
local session_id="${BASH_REMATCH[1]}"
|
|
1538
|
-
xml_json_ok "{\"valid\":true,\"type\":\"colony\",\"session_id\":\"$session_id\"}"
|
|
1539
|
-
elif [[ "$namespace_uri" == "http://aether.colony/schemas/pheromones" ]]; then
|
|
1540
|
-
xml_json_ok "{\"valid\":true,\"type\":\"schema\",\"session_id\":null}"
|
|
1541
|
-
else
|
|
1542
|
-
xml_json_ok "{\"valid\":false,\"type\":null,\"session_id\":null}"
|
|
1543
|
-
fi
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
# ============================================================================
|
|
1547
|
-
# Queen-Wisdom Markdown Generation (XSLT-based)
|
|
1548
|
-
# ============================================================================
|
|
1549
|
-
|
|
1550
|
-
# queen-wisdom-to-markdown: Convert queen-wisdom XML to markdown using XSLT
|
|
1551
|
-
# Usage: queen-wisdom-to-markdown <wisdom_xml_file> [output_md_file]
|
|
1552
|
-
# wisdom_xml_file: Path to queen-wisdom.xml
|
|
1553
|
-
# output_md_file: Optional path to write markdown output (default: stdout)
|
|
1554
|
-
# Returns: {"ok":true,"result":{"markdown":"...","path":"..."}} or error
|
|
1555
|
-
queen-wisdom-to-markdown() {
|
|
1556
|
-
local xml_file="${1:-}"
|
|
1557
|
-
local output_file="${2:-}"
|
|
1558
|
-
|
|
1559
|
-
# Validate arguments
|
|
1560
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
1561
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
1562
|
-
|
|
1563
|
-
# Check for xsltproc
|
|
1564
|
-
if [[ "$XSLTPROC_AVAILABLE" != "true" ]]; then
|
|
1565
|
-
xml_json_err "xsltproc not available. Install libxslt utilities."
|
|
1566
|
-
return 1
|
|
1567
|
-
fi
|
|
1568
|
-
|
|
1569
|
-
# Find XSLT file (check multiple locations)
|
|
1570
|
-
local xsl_file=""
|
|
1571
|
-
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
1572
|
-
|
|
1573
|
-
# Search paths for XSLT file
|
|
1574
|
-
local search_paths=(
|
|
1575
|
-
"$script_dir/queen-to-md.xsl"
|
|
1576
|
-
".aether/utils/queen-to-md.xsl"
|
|
1577
|
-
"runtime/utils/queen-to-md.xsl"
|
|
1578
|
-
"$HOME/.aether/system/utils/queen-to-md.xsl"
|
|
1579
|
-
)
|
|
1580
|
-
|
|
1581
|
-
for path in "${search_paths[@]}"; do
|
|
1582
|
-
if [[ -f "$path" ]]; then
|
|
1583
|
-
xsl_file="$path"
|
|
1584
|
-
break
|
|
1585
|
-
fi
|
|
1586
|
-
done
|
|
1587
|
-
|
|
1588
|
-
if [[ -z "$xsl_file" ]]; then
|
|
1589
|
-
xml_json_err "XSLT file queen-to-md.xsl not found in standard locations"
|
|
1590
|
-
return 1
|
|
1591
|
-
fi
|
|
1592
|
-
|
|
1593
|
-
# Validate XML against schema first
|
|
1594
|
-
local schema_file="$script_dir/../schemas/queen-wisdom.xsd"
|
|
1595
|
-
if [[ -f "$schema_file" ]]; then
|
|
1596
|
-
local validation
|
|
1597
|
-
validation=$(xml-validate "$xml_file" "$schema_file" 2>/dev/null)
|
|
1598
|
-
if ! echo "$validation" | jq -e '.result.valid' >/dev/null 2>&1; then
|
|
1599
|
-
xml_json_err "XML validation failed before conversion"
|
|
1600
|
-
return 1
|
|
1601
|
-
fi
|
|
1602
|
-
fi
|
|
1603
|
-
|
|
1604
|
-
# Perform XSLT transformation
|
|
1605
|
-
local markdown
|
|
1606
|
-
if ! markdown=$(xsltproc "$xsl_file" "$xml_file" 2>&1); then
|
|
1607
|
-
xml_json_err "XSLT transformation failed: $markdown"
|
|
1608
|
-
return 1
|
|
1609
|
-
fi
|
|
1610
|
-
|
|
1611
|
-
# Output handling
|
|
1612
|
-
if [[ -n "$output_file" ]]; then
|
|
1613
|
-
if echo "$markdown" > "$output_file"; then
|
|
1614
|
-
xml_json_ok "{\"markdown\":\"(written to file)\",\"path\":\"$output_file\"}"
|
|
1615
|
-
else
|
|
1616
|
-
xml_json_err "Failed to write to output file: $output_file"
|
|
1617
|
-
return 1
|
|
1618
|
-
fi
|
|
1619
|
-
else
|
|
1620
|
-
# Return markdown in JSON result
|
|
1621
|
-
local escaped_markdown
|
|
1622
|
-
escaped_markdown=$(echo "$markdown" | jq -Rs '.[:-1]')
|
|
1623
|
-
xml_json_ok "{\"markdown\":$escaped_markdown,\"path\":null}"
|
|
1624
|
-
fi
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
# ============================================================================
|
|
1628
|
-
# Queen-Wisdom Promotion Workflow
|
|
1629
|
-
# ============================================================================
|
|
1630
|
-
|
|
1631
|
-
# Get promotion threshold for a wisdom type
|
|
1632
|
-
# Usage: _get_promotion_threshold <type>
|
|
1633
|
-
# Returns: threshold value
|
|
1634
|
-
_get_promotion_threshold() {
|
|
1635
|
-
local wisdom_type="$1"
|
|
1636
|
-
case "$wisdom_type" in
|
|
1637
|
-
philosophy) echo "5" ;;
|
|
1638
|
-
pattern) echo "3" ;;
|
|
1639
|
-
redirect) echo "2" ;;
|
|
1640
|
-
stack) echo "1" ;;
|
|
1641
|
-
decree) echo "0" ;;
|
|
1642
|
-
*) echo "1" ;;
|
|
1643
|
-
esac
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
# queen-wisdom-validate-entry: Validate a single wisdom entry
|
|
1647
|
-
# Usage: queen-wisdom-validate-entry <xml_file> <entry_id>
|
|
1648
|
-
# Returns: {"ok":true,"result":{"valid":true,"errors":[],"warnings":[]}} or error
|
|
1649
|
-
queen-wisdom-validate-entry() {
|
|
1650
|
-
local xml_file="${1:-}"
|
|
1651
|
-
local entry_id="${2:-}"
|
|
1652
|
-
|
|
1653
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
1654
|
-
[[ -z "$entry_id" ]] && { xml_json_err "Missing entry ID argument"; return 1; }
|
|
1655
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
1656
|
-
|
|
1657
|
-
if [[ "$XMLLINT_AVAILABLE" != "true" ]]; then
|
|
1658
|
-
xml_json_err "xmllint not available"
|
|
1659
|
-
return 1
|
|
1660
|
-
fi
|
|
1661
|
-
|
|
1662
|
-
# Build XPath query to find the entry by ID
|
|
1663
|
-
local xpath_query="//*[@id='$entry_id']"
|
|
1664
|
-
local entry_xml
|
|
1665
|
-
entry_xml=$(xmllint --xpath "$xpath_query" "$xml_file" 2>/dev/null) || {
|
|
1666
|
-
xml_json_err "Entry not found with ID: $entry_id"
|
|
1667
|
-
return 1
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
# Initialize validation results
|
|
1671
|
-
local errors=()
|
|
1672
|
-
local warnings=()
|
|
1673
|
-
|
|
1674
|
-
# Extract attributes for validation
|
|
1675
|
-
local confidence domain source created_at content
|
|
1676
|
-
confidence=$(xmllint --xpath "string(//*[@id='$entry_id']/@confidence)" "$xml_file" 2>/dev/null)
|
|
1677
|
-
domain=$(xmllint --xpath "string(//*[@id='$entry_id']/@domain)" "$xml_file" 2>/dev/null)
|
|
1678
|
-
source=$(xmllint --xpath "string(//*[@id='$entry_id']/@source)" "$xml_file" 2>/dev/null)
|
|
1679
|
-
created_at=$(xmllint --xpath "string(//*[@id='$entry_id']/@created_at)" "$xml_file" 2>/dev/null)
|
|
1680
|
-
content=$(xmllint --xpath "string(//*[@id='$entry_id']/qw:content)" "$xml_file" 2>/dev/null || echo "")
|
|
1681
|
-
|
|
1682
|
-
# Validate confidence (0.0 to 1.0)
|
|
1683
|
-
if [[ -z "$confidence" ]]; then
|
|
1684
|
-
errors+=("Missing required attribute: confidence")
|
|
1685
|
-
elif ! [[ "$confidence" =~ ^0?\.[0-9]+$|^1\.0$ ]]; then
|
|
1686
|
-
errors+=("Invalid confidence value: $confidence (must be 0.0-1.0)")
|
|
1687
|
-
fi
|
|
1688
|
-
|
|
1689
|
-
# Validate domain (must be from allowed list)
|
|
1690
|
-
local valid_domains="architecture testing security performance ux process communication debugging general"
|
|
1691
|
-
if [[ -z "$domain" ]]; then
|
|
1692
|
-
errors+=("Missing required attribute: domain")
|
|
1693
|
-
elif [[ ! " $valid_domains " =~ " $domain " ]]; then
|
|
1694
|
-
errors+=("Invalid domain: $domain (must be one of: $valid_domains)")
|
|
1695
|
-
fi
|
|
1696
|
-
|
|
1697
|
-
# Validate source (must be from allowed list)
|
|
1698
|
-
local valid_sources="queen user colony oracle observation"
|
|
1699
|
-
if [[ -z "$source" ]]; then
|
|
1700
|
-
errors+=("Missing required attribute: source")
|
|
1701
|
-
elif [[ ! " $valid_sources " =~ " $source " ]]; then
|
|
1702
|
-
errors+=("Invalid source: $source (must be one of: $valid_sources)")
|
|
1703
|
-
fi
|
|
1704
|
-
|
|
1705
|
-
# Validate created_at (ISO 8601 format)
|
|
1706
|
-
if [[ -z "$created_at" ]]; then
|
|
1707
|
-
errors+=("Missing required attribute: created_at")
|
|
1708
|
-
elif ! [[ "$created_at" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2} ]]; then
|
|
1709
|
-
errors+=("Invalid timestamp format: $created_at (expected ISO 8601)")
|
|
1710
|
-
fi
|
|
1711
|
-
|
|
1712
|
-
# Validate content
|
|
1713
|
-
if [[ -z "$content" ]]; then
|
|
1714
|
-
errors+=("Missing required element: content")
|
|
1715
|
-
elif [[ ${#content} -lt 10 ]]; then
|
|
1716
|
-
warnings+=("Content is very short (${#content} chars) - consider expanding")
|
|
1717
|
-
fi
|
|
1718
|
-
|
|
1719
|
-
# Build JSON response
|
|
1720
|
-
local error_json="[]"
|
|
1721
|
-
local warning_json="[]"
|
|
1722
|
-
|
|
1723
|
-
if [[ ${#errors[@]} -gt 0 ]]; then
|
|
1724
|
-
error_json=$(printf '%s\n' "${errors[@]}" | jq -R . | jq -s .)
|
|
1725
|
-
fi
|
|
1726
|
-
|
|
1727
|
-
if [[ ${#warnings[@]} -gt 0 ]]; then
|
|
1728
|
-
warning_json=$(printf '%s\n' "${warnings[@]}" | jq -R . | jq -s .)
|
|
1729
|
-
fi
|
|
1730
|
-
|
|
1731
|
-
local valid="false"
|
|
1732
|
-
[[ ${#errors[@]} -eq 0 ]] && valid="true"
|
|
1733
|
-
|
|
1734
|
-
xml_json_ok "{\"valid\":$valid,\"errors\":$error_json,\"warnings\":$warning_json}"
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
# queen-wisdom-promote: Promote a wisdom entry with validation
|
|
1738
|
-
# Usage: queen-wisdom-promote <xml_file> <entry_id> [target_level]
|
|
1739
|
-
# xml_file: Path to queen-wisdom.xml
|
|
1740
|
-
# entry_id: ID of the entry to promote
|
|
1741
|
-
# target_level: Optional target promotion level (defaults to next level)
|
|
1742
|
-
# Returns: {"ok":true,"result":{"promoted":true,"from":"...","to":"...","evolution_log_updated":true}} or error
|
|
1743
|
-
queen-wisdom-promote() {
|
|
1744
|
-
local xml_file="${1:-}"
|
|
1745
|
-
local entry_id="${2:-}"
|
|
1746
|
-
local target_level="${3:-}"
|
|
1747
|
-
|
|
1748
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
1749
|
-
[[ -z "$entry_id" ]] && { xml_json_err "Missing entry ID argument"; return 1; }
|
|
1750
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
1751
|
-
|
|
1752
|
-
# First validate the entry
|
|
1753
|
-
local validation
|
|
1754
|
-
validation=$(queen-wisdom-validate-entry "$xml_file" "$entry_id" 2>&1)
|
|
1755
|
-
if ! echo "$validation" | jq -e '.result.valid' >/dev/null 2>&1; then
|
|
1756
|
-
local errors
|
|
1757
|
-
errors=$(echo "$validation" | jq -r '.result.errors | join("; ")')
|
|
1758
|
-
xml_json_err "Validation failed: $errors"
|
|
1759
|
-
return 1
|
|
1760
|
-
fi
|
|
1761
|
-
|
|
1762
|
-
# Get current entry type and applied count
|
|
1763
|
-
local current_type applied_count
|
|
1764
|
-
# Determine entry type by which container it's in
|
|
1765
|
-
if xmllint --xpath "//qw:philosophies/qw:philosophy[@id='$entry_id']" "$xml_file" >/dev/null 2>&1; then
|
|
1766
|
-
current_type="philosophy"
|
|
1767
|
-
applied_count=$(xmllint --xpath "string(//qw:philosophy[@id='$entry_id']/@applied_count)" "$xml_file" 2>/dev/null || echo "0")
|
|
1768
|
-
elif xmllint --xpath "//qw:patterns/qw:pattern[@id='$entry_id']" "$xml_file" >/dev/null 2>&1; then
|
|
1769
|
-
current_type="pattern"
|
|
1770
|
-
applied_count=$(xmllint --xpath "string(//qw:pattern[@id='$entry_id']/@applied_count)" "$xml_file" 2>/dev/null || echo "0")
|
|
1771
|
-
elif xmllint --xpath "//qw:redirects/qw:redirect[@id='$entry_id']" "$xml_file" >/dev/null 2>&1; then
|
|
1772
|
-
current_type="redirect"
|
|
1773
|
-
applied_count=$(xmllint --xpath "string(//qw:redirect[@id='$entry_id']/@applied_count)" "$xml_file" 2>/dev/null || echo "0")
|
|
1774
|
-
elif xmllint --xpath "//qw:stack-wisdom/qw:wisdom[@id='$entry_id']" "$xml_file" >/dev/null 2>&1; then
|
|
1775
|
-
current_type="stack"
|
|
1776
|
-
applied_count=$(xmllint --xpath "string(//qw:stack-wisdom/qw:wisdom[@id='$entry_id']/@applied_count)" "$xml_file" 2>/dev/null || echo "0")
|
|
1777
|
-
elif xmllint --xpath "//qw:decrees/qw:decree[@id='$entry_id']" "$xml_file" >/dev/null 2>&1; then
|
|
1778
|
-
current_type="decree"
|
|
1779
|
-
applied_count=$(xmllint --xpath "string(//qw:decree[@id='$entry_id']/@applied_count)" "$xml_file" 2>/dev/null || echo "0")
|
|
1780
|
-
else
|
|
1781
|
-
xml_json_err "Entry not found: $entry_id"
|
|
1782
|
-
return 1
|
|
1783
|
-
fi
|
|
1784
|
-
|
|
1785
|
-
# Check promotion threshold
|
|
1786
|
-
local threshold
|
|
1787
|
-
threshold=$(_get_promotion_threshold "$current_type")
|
|
1788
|
-
|
|
1789
|
-
if [[ "$applied_count" -lt "$threshold" ]]; then
|
|
1790
|
-
xml_json_err "Not enough validations for promotion: $applied_count < $threshold (required for $current_type)"
|
|
1791
|
-
return 1
|
|
1792
|
-
fi
|
|
1793
|
-
|
|
1794
|
-
# Get colony ID from metadata or use "unknown"
|
|
1795
|
-
local colony_id
|
|
1796
|
-
colony_id=$(xmllint --xpath "string(//qw:metadata/qw:colony_id)" "$xml_file" 2>/dev/null || echo "unknown")
|
|
1797
|
-
|
|
1798
|
-
# Create evolution log entry
|
|
1799
|
-
local timestamp
|
|
1800
|
-
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1801
|
-
|
|
1802
|
-
# Note: In a full implementation, this would modify the XML file
|
|
1803
|
-
# For now, we return success and indicate what would happen
|
|
1804
|
-
xml_json_ok "{\"promoted\":true,\"entry_id\":\"$entry_id\",\"type\":\"$current_type\",\"from_applied\":$applied_count,\"threshold\":$threshold,\"colony\":\"$colony_id\",\"timestamp\":\"$timestamp\",\"note\":\"Evolution log update requires XML editing capability\"}"
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
# queen-wisdom-import: Import wisdom from markdown QUEEN.md to XML
|
|
1808
|
-
# Usage: queen-wisdom-import <queen_md_file> [output_xml_file]
|
|
1809
|
-
# Returns: {"ok":true,"result":{"imported":5,"xml":"...","path":"..."}} or error
|
|
1810
|
-
queen-wisdom-import() {
|
|
1811
|
-
local md_file="${1:-}"
|
|
1812
|
-
local output_file="${2:-"queen-wisdom-imported.xml"}"
|
|
1813
|
-
|
|
1814
|
-
[[ -z "$md_file" ]] && { xml_json_err "Missing markdown file argument"; return 1; }
|
|
1815
|
-
[[ -f "$md_file" ]] || { xml_json_err "Markdown file not found: $md_file"; return 1; }
|
|
1816
|
-
|
|
1817
|
-
# Extract metadata from JSON block
|
|
1818
|
-
local version last_evolved colonies
|
|
1819
|
-
version=$(grep -A20 'METADATA' "$md_file" | grep '"version"' | sed 's/.*: "\([^"]*\)".*/\1/')
|
|
1820
|
-
last_evolved=$(grep -A20 'METADATA' "$md_file" | grep '"last_evolved"' | sed 's/.*: "\([^"]*\)".*/\1/')
|
|
1821
|
-
|
|
1822
|
-
# Generate timestamps
|
|
1823
|
-
local created modified
|
|
1824
|
-
created="${last_evolved:-$(date -u +"%Y-%m-%dT%H:%M:%SZ")}"
|
|
1825
|
-
modified=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1826
|
-
|
|
1827
|
-
# Start building XML
|
|
1828
|
-
local xml='<?xml version="1.0" encoding="UTF-8"?>'
|
|
1829
|
-
xml+=$'\n<queen-wisdom xmlns:qw="http://aether.colony/schemas/queen-wisdom/1.0">'
|
|
1830
|
-
|
|
1831
|
-
# Metadata section
|
|
1832
|
-
xml+=$'\n <metadata>'
|
|
1833
|
-
xml+=$'\n <version>'"${version:-1.0.0}"'</version>'
|
|
1834
|
-
xml+=$'\n <created>'"$created"'</created>'
|
|
1835
|
-
xml+=$'\n <modified>'"$modified"'</modified>'
|
|
1836
|
-
xml+=$'\n <colony_id>imported</colony_id>'
|
|
1837
|
-
xml+=$'\n </metadata>'
|
|
1838
|
-
|
|
1839
|
-
# Parse sections using simple grep/sed patterns
|
|
1840
|
-
# Note: This is a basic implementation - full parsing would require more sophisticated handling
|
|
1841
|
-
|
|
1842
|
-
local imported_count=0
|
|
1843
|
-
|
|
1844
|
-
# Extract philosophies (simplified parsing)
|
|
1845
|
-
xml+=$'\n <philosophies>'
|
|
1846
|
-
while IFS= read -r line; do
|
|
1847
|
-
# Look for lines starting with "- **" and extract content
|
|
1848
|
-
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*\*\* ]]; then
|
|
1849
|
-
local id timestamp content
|
|
1850
|
-
id=$(echo "$line" | sed -n 's/.*\*\*\([^*]*\)\*\*.*/\1/p')
|
|
1851
|
-
timestamp=$(echo "$line" | sed -n 's/.*(\([^)]*\)).*/\1/p')
|
|
1852
|
-
content=$(echo "$line" | sed -n 's/.*):[[:space:]]*\(.*\)/\1/p')
|
|
1853
|
-
if [[ -n "$id" && -n "$content" ]]; then
|
|
1854
|
-
xml+=$'\n <philosophy id="'"$id"'" confidence="0.8" domain="general" source="observation" created_at="'"${timestamp:-$modified}"'">'
|
|
1855
|
-
xml+=$'\n <content>'"$content"'</content>'
|
|
1856
|
-
xml+=$'\n </philosophy>'
|
|
1857
|
-
((imported_count++))
|
|
1858
|
-
fi
|
|
1859
|
-
fi
|
|
1860
|
-
done < <(sed -n '/## 📜 Philosophies/,/## /p' "$md_file" 2>/dev/null | tail -n +4)
|
|
1861
|
-
xml+=$'\n </philosophies>'
|
|
1862
|
-
|
|
1863
|
-
# Extract patterns (simplified parsing)
|
|
1864
|
-
xml+=$'\n <patterns>'
|
|
1865
|
-
while IFS= read -r line; do
|
|
1866
|
-
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*\*\* ]]; then
|
|
1867
|
-
local id timestamp content
|
|
1868
|
-
id=$(echo "$line" | sed -n 's/.*\*\*\([^*]*\)\*\*.*/\1/p')
|
|
1869
|
-
timestamp=$(echo "$line" | sed -n 's/.*(\([^)]*\)).*/\1/p')
|
|
1870
|
-
content=$(echo "$line" | sed -n 's/.*):[[:space:]]*\(.*\)/\1/p')
|
|
1871
|
-
if [[ -n "$id" && -n "$content" ]]; then
|
|
1872
|
-
xml+=$'\n <pattern id="'"$id"'" confidence="0.7" domain="general" source="observation" created_at="'"${timestamp:-$modified}"'">'
|
|
1873
|
-
xml+=$'\n <content>'"$content"'</content>'
|
|
1874
|
-
xml+=$'\n </pattern>'
|
|
1875
|
-
((imported_count++))
|
|
1876
|
-
fi
|
|
1877
|
-
fi
|
|
1878
|
-
done < <(sed -n '/## 🧭 Patterns/,/## /p' "$md_file" 2>/dev/null | tail -n +4)
|
|
1879
|
-
xml+=$'\n </patterns>'
|
|
1880
|
-
|
|
1881
|
-
# Similar for other sections (simplified for brevity)
|
|
1882
|
-
xml+=$'\n <redirects />'
|
|
1883
|
-
xml+=$'\n <stack-wisdom />'
|
|
1884
|
-
xml+=$'\n <decrees />'
|
|
1885
|
-
|
|
1886
|
-
# Evolution log
|
|
1887
|
-
xml+=$'\n <evolution-log>'
|
|
1888
|
-
xml+=$'\n <entry timestamp="'"$modified"'" colony="import" action="imported" type="markdown">'
|
|
1889
|
-
xml+=$'\n <note>Imported from '"$md_file"' with '"$imported_count"' entries</note>'
|
|
1890
|
-
xml+=$'\n </entry>'
|
|
1891
|
-
xml+=$'\n </evolution-log>'
|
|
1892
|
-
|
|
1893
|
-
xml+=$'\n</queen-wisdom>'
|
|
1894
|
-
|
|
1895
|
-
# Write output
|
|
1896
|
-
echo "$xml" > "$output_file"
|
|
1897
|
-
|
|
1898
|
-
xml_json_ok "{\"imported\":$imported_count,\"xml\":\"(written to file)\",\"path\":\"$output_file\"}"
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
# ============================================================================
|
|
1902
|
-
# Prompt XML Conversion
|
|
1903
|
-
# ============================================================================
|
|
1904
|
-
|
|
1905
|
-
# prompt-to-xml: Convert a markdown prompt file to structured XML
|
|
1906
|
-
# Usage: prompt-to-xml <markdown_file> [output_xml_file]
|
|
1907
|
-
# Returns: {"ok":true,"result":{"xml":"...","path":"...","elements_extracted":N}} or error
|
|
1908
|
-
prompt-to-xml() {
|
|
1909
|
-
local md_file="${1:-}"
|
|
1910
|
-
local output_file="${2:-}"
|
|
1911
|
-
|
|
1912
|
-
[[ -z "$md_file" ]] && { xml_json_err "Missing markdown file argument"; return 1; }
|
|
1913
|
-
[[ -f "$md_file" ]] || { xml_json_err "Markdown file not found: $md_file"; return 1; }
|
|
1914
|
-
|
|
1915
|
-
# Extract prompt name from filename
|
|
1916
|
-
local prompt_name
|
|
1917
|
-
prompt_name=$(basename "$md_file" .md)
|
|
1918
|
-
|
|
1919
|
-
# Initialize XML parts
|
|
1920
|
-
local xml='<?xml version="1.0" encoding="UTF-8"?>'
|
|
1921
|
-
xml+=$'\n<aether-prompt xmlns:ap="http://aether.colony/schemas/prompt/1.0">'
|
|
1922
|
-
|
|
1923
|
-
# Metadata
|
|
1924
|
-
xml+=$'\n <metadata>'
|
|
1925
|
-
xml+=$'\n <version>1.0.0</version>'
|
|
1926
|
-
xml+=$'\n <created>'"$(date -u +"%Y-%m-%dT%H:%M:%SZ")"'</created>'
|
|
1927
|
-
xml+=$'\n </metadata>'
|
|
1928
|
-
|
|
1929
|
-
# Name and type detection
|
|
1930
|
-
xml+=$'\n <name>'"$prompt_name"'</name>'
|
|
1931
|
-
|
|
1932
|
-
# Detect type from content patterns
|
|
1933
|
-
local prompt_type="command"
|
|
1934
|
-
if grep -q "worker\|caste\|Builder\|Watcher\|Scout" "$md_file" 2>/dev/null; then
|
|
1935
|
-
prompt_type="worker"
|
|
1936
|
-
elif grep -q "agent\|Agent" "$md_file" 2>/dev/null; then
|
|
1937
|
-
prompt_type="agent"
|
|
1938
|
-
fi
|
|
1939
|
-
xml+=$'\n <type>'"$prompt_type"'</type>'
|
|
1940
|
-
|
|
1941
|
-
# Try to detect caste for worker prompts
|
|
1942
|
-
local caste=""
|
|
1943
|
-
if [[ "$prompt_type" == "worker" ]]; then
|
|
1944
|
-
if grep -qi "builder" "$md_file"; then
|
|
1945
|
-
caste="builder"
|
|
1946
|
-
elif grep -qi "watcher" "$md_file"; then
|
|
1947
|
-
caste="watcher"
|
|
1948
|
-
elif grep -qi "scout" "$md_file"; then
|
|
1949
|
-
caste="scout"
|
|
1950
|
-
elif grep -qi "chaos" "$md_file"; then
|
|
1951
|
-
caste="chaos"
|
|
1952
|
-
elif grep -qi "oracle" "$md_file"; then
|
|
1953
|
-
caste="oracle"
|
|
1954
|
-
elif grep -qi "architect" "$md_file"; then
|
|
1955
|
-
caste="architect"
|
|
1956
|
-
fi
|
|
1957
|
-
[[ -n "$caste" ]] && xml+=$'\n <caste>'"$caste"'</caste>'
|
|
1958
|
-
fi
|
|
1959
|
-
|
|
1960
|
-
# Extract objective (first H1 or first paragraph)
|
|
1961
|
-
local objective
|
|
1962
|
-
objective=$(grep -m1 "^# " "$md_file" 2>/dev/null | sed 's/^# //' || head -1 "$md_file")
|
|
1963
|
-
xml+=$'\n <objective>'"$(xml_escape_content "$objective")"'</objective>'
|
|
1964
|
-
|
|
1965
|
-
# Extract requirements (## Requirements or numbered lists)
|
|
1966
|
-
xml+=$'\n <requirements>'
|
|
1967
|
-
local req_count=0
|
|
1968
|
-
while IFS= read -r line; do
|
|
1969
|
-
# Check for list items (bullet or numbered)
|
|
1970
|
-
if [[ "$line" =~ ^[[:space:]]*[-*][[:space:]] ]] || [[ "$line" =~ ^[[:space:]]*[0-9]+\.[[:space:]] ]]; then
|
|
1971
|
-
local req_desc
|
|
1972
|
-
req_desc=$(echo "$line" | sed -E 's/^[[:space:]]*[-*][[:space:]]+//' | sed -E 's/^[[:space:]]*[0-9]+\.[[:space:]]+//')
|
|
1973
|
-
((req_count++))
|
|
1974
|
-
xml+=$'\n <requirement id="req_'"$req_count"'" priority="normal">'
|
|
1975
|
-
xml+=$'\n <description>'"$(xml_escape_content "$req_desc")"'</description>'
|
|
1976
|
-
xml+=$'\n </requirement>'
|
|
1977
|
-
fi
|
|
1978
|
-
done < <(sed -n '/## Requirement/,/## /p' "$md_file" 2>/dev/null | tail -n +2)
|
|
1979
|
-
|
|
1980
|
-
# If no requirements section found, add a default one
|
|
1981
|
-
if [[ $req_count -eq 0 ]]; then
|
|
1982
|
-
xml+=$'\n <requirement id="req_1" priority="normal">'
|
|
1983
|
-
xml+=$'\n <description>Follow the instructions in this prompt</description>'
|
|
1984
|
-
xml+=$'\n </requirement>'
|
|
1985
|
-
fi
|
|
1986
|
-
xml+=$'\n </requirements>'
|
|
1987
|
-
|
|
1988
|
-
# Output specification
|
|
1989
|
-
xml+=$'\n <output>'
|
|
1990
|
-
xml+=$'\n <format>Markdown</format>'
|
|
1991
|
-
xml+=$'\n </output>'
|
|
1992
|
-
|
|
1993
|
-
# Verification
|
|
1994
|
-
xml+=$'\n <verification>'
|
|
1995
|
-
xml+=$'\n <method>Check output meets success criteria</method>'
|
|
1996
|
-
xml+=$'\n </verification>'
|
|
1997
|
-
|
|
1998
|
-
# Success criteria
|
|
1999
|
-
xml+=$'\n <success_criteria>'
|
|
2000
|
-
xml+=$'\n <criterion id="crit_1" required="true">'
|
|
2001
|
-
xml+=$'\n <description>Task completed as specified</description>'
|
|
2002
|
-
xml+=$'\n </criterion>'
|
|
2003
|
-
xml+=$'\n </success_criteria>'
|
|
2004
|
-
|
|
2005
|
-
xml+=$'\n</aether-prompt>'
|
|
2006
|
-
|
|
2007
|
-
# Output handling
|
|
2008
|
-
if [[ -n "$output_file" ]]; then
|
|
2009
|
-
echo "$xml" > "$output_file"
|
|
2010
|
-
xml_json_ok "{\"xml\":\"(written to file)\",\"path\":\"$output_file\",\"elements_extracted\":$((req_count + 5))}"
|
|
2011
|
-
else
|
|
2012
|
-
local escaped_xml
|
|
2013
|
-
escaped_xml=$(echo "$xml" | jq -Rs '.[:-1]')
|
|
2014
|
-
xml_json_ok "{\"xml\":$escaped_xml,\"path\":null,\"elements_extracted\":$((req_count + 5))}"
|
|
2015
|
-
fi
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
# prompt-from-xml: Convert XML prompt to markdown format
|
|
2019
|
-
# Usage: prompt-from-xml <xml_file> [output_md_file]
|
|
2020
|
-
# Returns: {"ok":true,"result":{"markdown":"...","path":"..."}} or error
|
|
2021
|
-
prompt-from-xml() {
|
|
2022
|
-
local xml_file="${1:-}"
|
|
2023
|
-
local output_file="${2:-}"
|
|
2024
|
-
|
|
2025
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
2026
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
2027
|
-
|
|
2028
|
-
if [[ "$XMLLINT_AVAILABLE" != "true" ]]; then
|
|
2029
|
-
xml_json_err "xmllint not available"
|
|
2030
|
-
return 1
|
|
2031
|
-
fi
|
|
2032
|
-
|
|
2033
|
-
# Validate against schema
|
|
2034
|
-
local schema_file
|
|
2035
|
-
schema_file="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../schemas/prompt.xsd"
|
|
2036
|
-
|
|
2037
|
-
if [[ -f "$schema_file" ]]; then
|
|
2038
|
-
local validation
|
|
2039
|
-
validation=$(xml-validate "$xml_file" "$schema_file" 2>/dev/null)
|
|
2040
|
-
if ! echo "$validation" | jq -e '.result.valid' >/dev/null 2>&1; then
|
|
2041
|
-
xml_json_err "XML validation failed against prompt.xsd schema"
|
|
2042
|
-
return 1
|
|
2043
|
-
fi
|
|
2044
|
-
fi
|
|
2045
|
-
|
|
2046
|
-
# Extract fields using XPath
|
|
2047
|
-
local name type caste objective
|
|
2048
|
-
name=$(xmllint --xpath "string(//ap:name)" "$xml_file" 2>/dev/null || echo "Unnamed")
|
|
2049
|
-
type=$(xmllint --xpath "string(//ap:type)" "$xml_file" 2>/dev/null || echo "command")
|
|
2050
|
-
caste=$(xmllint --xpath "string(//ap:caste)" "$xml_file" 2>/dev/null || echo "")
|
|
2051
|
-
objective=$(xmllint --xpath "string(//ap:objective)" "$xml_file" 2>/dev/null || echo "")
|
|
2052
|
-
|
|
2053
|
-
# Build markdown
|
|
2054
|
-
local md="# $name"
|
|
2055
|
-
[[ -n "$caste" ]] && md+=" ($caste $type)"
|
|
2056
|
-
md+=$'\n\n'
|
|
2057
|
-
|
|
2058
|
-
md+="## Objective"
|
|
2059
|
-
md+=$'\n\n'
|
|
2060
|
-
md+="$objective"
|
|
2061
|
-
md+=$'\n\n'
|
|
2062
|
-
|
|
2063
|
-
# Requirements
|
|
2064
|
-
local req_count
|
|
2065
|
-
req_count=$(xmllint --xpath "count(//ap:requirements/ap:requirement)" "$xml_file" 2>/dev/null || echo "0")
|
|
2066
|
-
if [[ "$req_count" -gt 0 ]]; then
|
|
2067
|
-
md+="## Requirements"
|
|
2068
|
-
md+=$'\n\n'
|
|
2069
|
-
|
|
2070
|
-
for i in $(seq 1 "$req_count"); do
|
|
2071
|
-
local req_desc req_priority
|
|
2072
|
-
req_desc=$(xmllint --xpath "string(//ap:requirements/ap:requirement[$i]/ap:description)" "$xml_file" 2>/dev/null || echo "")
|
|
2073
|
-
req_priority=$(xmllint --xpath "string(//ap:requirements/ap:requirement[$i]/@priority)" "$xml_file" 2>/dev/null || echo "normal")
|
|
2074
|
-
|
|
2075
|
-
if [[ -n "$req_desc" ]]; then
|
|
2076
|
-
md+="$i. [$req_priority] $req_desc"
|
|
2077
|
-
md+=$'\n'
|
|
2078
|
-
fi
|
|
2079
|
-
done
|
|
2080
|
-
md+=$'\n'
|
|
2081
|
-
fi
|
|
2082
|
-
|
|
2083
|
-
# Constraints
|
|
2084
|
-
local constraint_count
|
|
2085
|
-
constraint_count=$(xmllint --xpath "count(//ap:constraints/ap:constraint)" "$xml_file" 2>/dev/null || echo "0")
|
|
2086
|
-
if [[ "$constraint_count" -gt 0 ]]; then
|
|
2087
|
-
md+="## Constraints"
|
|
2088
|
-
md+=$'\n\n'
|
|
2089
|
-
|
|
2090
|
-
for i in $(seq 1 "$constraint_count"); do
|
|
2091
|
-
local constraint_rule constraint_strength
|
|
2092
|
-
constraint_rule=$(xmllint --xpath "string(//ap:constraints/ap:constraint[$i]/ap:rule)" "$xml_file" 2>/dev/null || echo "")
|
|
2093
|
-
constraint_strength=$(xmllint --xpath "string(//ap:constraints/ap:constraint[$i]/@strength)" "$xml_file" 2>/dev/null || echo "should")
|
|
2094
|
-
|
|
2095
|
-
if [[ -n "$constraint_rule" ]]; then
|
|
2096
|
-
md+="- [$constraint_strength] $constraint_rule"
|
|
2097
|
-
md+=$'\n'
|
|
2098
|
-
fi
|
|
2099
|
-
done
|
|
2100
|
-
md+=$'\n'
|
|
2101
|
-
fi
|
|
2102
|
-
|
|
2103
|
-
# Output
|
|
2104
|
-
local output_format
|
|
2105
|
-
output_format=$(xmllint --xpath "string(//ap:output/ap:format)" "$xml_file" 2>/dev/null || echo "Markdown")
|
|
2106
|
-
md+="## Output"
|
|
2107
|
-
md+=$'\n\n'
|
|
2108
|
-
md+="Format: $output_format"
|
|
2109
|
-
md+=$'\n\n'
|
|
2110
|
-
|
|
2111
|
-
# Verification
|
|
2112
|
-
md+="## Verification"
|
|
2113
|
-
md+=$'\n\n'
|
|
2114
|
-
local verification_method
|
|
2115
|
-
verification_method=$(xmllint --xpath "string(//ap:verification/ap:method)" "$xml_file" 2>/dev/null || echo "Manual review")
|
|
2116
|
-
md+="$verification_method"
|
|
2117
|
-
md+=$'\n\n'
|
|
2118
|
-
|
|
2119
|
-
# Success criteria
|
|
2120
|
-
md+="## Success Criteria"
|
|
2121
|
-
md+=$'\n\n'
|
|
2122
|
-
|
|
2123
|
-
local crit_count
|
|
2124
|
-
crit_count=$(xmllint --xpath "count(//ap:success_criteria/ap:criterion)" "$xml_file" 2>/dev/null || echo "0")
|
|
2125
|
-
for i in $(seq 1 "$crit_count"); do
|
|
2126
|
-
local crit_desc crit_required
|
|
2127
|
-
crit_desc=$(xmllint --xpath "string(//ap:success_criteria/ap:criterion[$i]/ap:description)" "$xml_file" 2>/dev/null || echo "")
|
|
2128
|
-
crit_required=$(xmllint --xpath "string(//ap:success_criteria/ap:criterion[$i]/@required)" "$xml_file" 2>/dev/null || echo "true")
|
|
2129
|
-
|
|
2130
|
-
if [[ -n "$crit_desc" ]]; then
|
|
2131
|
-
[[ "$crit_required" == "true" ]] && md+="- [required] " || md+="- [optional] "
|
|
2132
|
-
md+="$crit_desc"
|
|
2133
|
-
md+=$'\n'
|
|
2134
|
-
fi
|
|
2135
|
-
done
|
|
2136
|
-
|
|
2137
|
-
# Output handling
|
|
2138
|
-
if [[ -n "$output_file" ]]; then
|
|
2139
|
-
echo "$md" > "$output_file"
|
|
2140
|
-
xml_json_ok "{\"markdown\":\"(written to file)\",\"path\":\"$output_file\"}"
|
|
2141
|
-
else
|
|
2142
|
-
local escaped_md
|
|
2143
|
-
escaped_md=$(echo "$md" | jq -Rs '.[:-1]')
|
|
2144
|
-
xml_json_ok "{\"markdown\":$escaped_md,\"path\":null}"
|
|
2145
|
-
fi
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
# prompt-validate: Validate a prompt XML file against the schema
|
|
2149
|
-
# Usage: prompt-validate <xml_file>
|
|
2150
|
-
# Returns: {"ok":true,"result":{"valid":true,"errors":[]}} or error
|
|
2151
|
-
prompt-validate() {
|
|
2152
|
-
local xml_file="${1:-}"
|
|
2153
|
-
|
|
2154
|
-
[[ -z "$xml_file" ]] && { xml_json_err "Missing XML file argument"; return 1; }
|
|
2155
|
-
[[ -f "$xml_file" ]] || { xml_json_err "XML file not found: $xml_file"; return 1; }
|
|
2156
|
-
|
|
2157
|
-
local schema_file
|
|
2158
|
-
schema_file="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../schemas/prompt.xsd"
|
|
2159
|
-
|
|
2160
|
-
[[ -f "$schema_file" ]] || { xml_json_err "Schema file not found: $schema_file"; return 1; }
|
|
2161
|
-
|
|
2162
|
-
xml-validate "$xml_file" "$schema_file"
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
# Helper function to escape XML content
|
|
2166
|
-
xml_escape_content() {
|
|
2167
|
-
local content="$1"
|
|
2168
|
-
# Basic XML escaping
|
|
2169
|
-
content="${content//&/&}"
|
|
2170
|
-
content="${content//\</<}"
|
|
2171
|
-
content="${content//\>/>}"
|
|
2172
|
-
content="${content//\"/"}"
|
|
2173
|
-
echo "$content"
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
# ============================================================================
|
|
2177
|
-
# Export Functions
|
|
2178
|
-
# ============================================================================
|
|
2179
|
-
|
|
2180
|
-
# Functions are available when this file is sourced.
|
|
2181
|
-
# Export is disabled by default to avoid polluting stdout during tests.
|
|
2182
|
-
# Set XML_UTILS_EXPORT=1 to enable function export for subshells.
|
|
2183
|
-
if [[ "${XML_UTILS_EXPORT:-}" == "1" ]]; then
|
|
2184
|
-
export -f xml-validate xml-well-formed xml-to-json json-to-xml 2>/dev/null || true
|
|
2185
|
-
export -f xml-query xml-query-attr xml-merge xml-format 2>/dev/null || true
|
|
2186
|
-
export -f xml-escape xml-unescape xml-detect-tools 2>/dev/null || true
|
|
2187
|
-
export -f pheromone-to-xml pheromone-from-xml pheromone-export 2>/dev/null || true
|
|
2188
|
-
export -f queen-wisdom-to-xml queen-wisdom-from-xml 2>/dev/null || true
|
|
2189
|
-
export -f queen-wisdom-to-markdown queen-wisdom-validate-entry 2>/dev/null || true
|
|
2190
|
-
export -f queen-wisdom-promote queen-wisdom-import 2>/dev/null || true
|
|
2191
|
-
export -f registry-to-xml registry-from-xml 2>/dev/null || true
|
|
2192
|
-
export -f generate-colony-namespace generate-cross-colony-prefix 2>/dev/null || true
|
|
2193
|
-
export -f prefix-pheromone-id extract-session-from-namespace 2>/dev/null || true
|
|
2194
|
-
export -f validate-colony-namespace 2>/dev/null || true
|
|
2195
|
-
export -f prompt-to-xml prompt-from-xml prompt-validate 2>/dev/null || true
|
|
2196
|
-
fi
|