aether-colony 5.0.0 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aether/aether-utils.sh +3226 -3345
- package/.aether/agents-claude/aether-ambassador.md +265 -0
- package/.aether/agents-claude/aether-archaeologist.md +327 -0
- package/.aether/agents-claude/aether-architect.md +236 -0
- package/.aether/agents-claude/aether-auditor.md +271 -0
- package/.aether/agents-claude/aether-builder.md +224 -0
- package/.aether/agents-claude/aether-chaos.md +269 -0
- package/.aether/agents-claude/aether-chronicler.md +305 -0
- package/.aether/agents-claude/aether-gatekeeper.md +330 -0
- package/.aether/agents-claude/aether-includer.md +374 -0
- package/.aether/agents-claude/aether-keeper.md +272 -0
- package/.aether/agents-claude/aether-measurer.md +322 -0
- package/.aether/agents-claude/aether-oracle.md +237 -0
- package/.aether/agents-claude/aether-probe.md +211 -0
- package/.aether/agents-claude/aether-queen.md +330 -0
- package/.aether/agents-claude/aether-route-setter.md +178 -0
- package/.aether/agents-claude/aether-sage.md +418 -0
- package/.aether/agents-claude/aether-scout.md +179 -0
- package/.aether/agents-claude/aether-surveyor-disciplines.md +417 -0
- package/.aether/agents-claude/aether-surveyor-nest.md +355 -0
- package/.aether/agents-claude/aether-surveyor-pathogens.md +289 -0
- package/.aether/agents-claude/aether-surveyor-provisions.md +360 -0
- package/.aether/agents-claude/aether-tracker.md +270 -0
- package/.aether/agents-claude/aether-watcher.md +280 -0
- package/.aether/agents-claude/aether-weaver.md +248 -0
- package/.aether/commands/archaeology.yaml +653 -0
- package/.aether/commands/build.yaml +1221 -0
- package/.aether/commands/chaos.yaml +653 -0
- package/.aether/commands/colonize.yaml +442 -0
- package/.aether/commands/continue.yaml +1484 -0
- package/.aether/commands/council.yaml +509 -0
- package/.aether/commands/data-clean.yaml +80 -0
- package/.aether/commands/dream.yaml +275 -0
- package/.aether/commands/entomb.yaml +863 -0
- package/.aether/commands/export-signals.yaml +64 -0
- package/.aether/commands/feedback.yaml +158 -0
- package/.aether/commands/flag.yaml +160 -0
- package/.aether/commands/flags.yaml +177 -0
- package/.aether/commands/focus.yaml +112 -0
- package/.aether/commands/help.yaml +167 -0
- package/.aether/commands/history.yaml +137 -0
- package/.aether/commands/import-signals.yaml +79 -0
- package/.aether/commands/init.yaml +502 -0
- package/.aether/commands/insert-phase.yaml +102 -0
- package/.aether/commands/interpret.yaml +285 -0
- package/.aether/commands/lay-eggs.yaml +224 -0
- package/.aether/commands/maturity.yaml +122 -0
- package/.aether/commands/memory-details.yaml +74 -0
- package/.aether/commands/migrate-state.yaml +174 -0
- package/.aether/commands/oracle.yaml +1224 -0
- package/.aether/commands/organize.yaml +446 -0
- package/.aether/commands/patrol.yaml +621 -0
- package/.aether/commands/pause-colony.yaml +424 -0
- package/.aether/commands/phase.yaml +124 -0
- package/.aether/commands/pheromones.yaml +153 -0
- package/.aether/commands/plan.yaml +1364 -0
- package/.aether/commands/preferences.yaml +63 -0
- package/.aether/commands/quick.yaml +104 -0
- package/.aether/commands/redirect.yaml +123 -0
- package/.aether/commands/resume-colony.yaml +375 -0
- package/.aether/commands/resume.yaml +407 -0
- package/.aether/commands/run.yaml +229 -0
- package/.aether/commands/seal.yaml +1214 -0
- package/.aether/commands/skill-create.yaml +337 -0
- package/.aether/commands/status.yaml +408 -0
- package/.aether/commands/swarm.yaml +352 -0
- package/.aether/commands/tunnels.yaml +814 -0
- package/.aether/commands/update.yaml +131 -0
- package/.aether/commands/verify-castes.yaml +159 -0
- package/.aether/commands/watch.yaml +454 -0
- package/.aether/docs/INCIDENT_TEMPLATE.md +32 -0
- package/.aether/docs/QUEEN-SYSTEM.md +11 -11
- package/.aether/docs/README.md +32 -2
- package/.aether/docs/command-playbooks/README.md +23 -0
- package/.aether/docs/command-playbooks/build-complete.md +349 -0
- package/.aether/docs/command-playbooks/build-context.md +282 -0
- package/.aether/docs/command-playbooks/build-full.md +1683 -0
- package/.aether/docs/command-playbooks/build-prep.md +284 -0
- package/.aether/docs/command-playbooks/build-verify.md +405 -0
- package/.aether/docs/command-playbooks/build-wave.md +749 -0
- package/.aether/docs/command-playbooks/continue-advance.md +524 -0
- package/.aether/docs/command-playbooks/continue-finalize.md +447 -0
- package/.aether/docs/command-playbooks/continue-full.md +1725 -0
- package/.aether/docs/command-playbooks/continue-gates.md +686 -0
- package/.aether/docs/command-playbooks/continue-verify.md +407 -0
- package/.aether/docs/context-continuity.md +84 -0
- package/.aether/docs/disciplines/DISCIPLINES.md +9 -7
- package/.aether/docs/error-codes.md +1 -1
- package/.aether/docs/known-issues.md +34 -173
- package/.aether/docs/pheromones.md +86 -6
- package/.aether/docs/plans/pheromone-display-plan.md +257 -0
- package/.aether/docs/queen-commands.md +10 -9
- package/.aether/docs/source-of-truth-map.md +132 -0
- package/.aether/docs/xml-utilities.md +47 -0
- package/.aether/rules/aether-colony.md +23 -13
- package/.aether/scripts/incident-test-add.sh +47 -0
- package/.aether/scripts/weekly-audit.sh +79 -0
- package/.aether/skills/.index.json +649 -0
- package/.aether/skills/colony/.manifest.json +16 -0
- package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
- package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
- package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
- package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
- package/.aether/skills/colony/context-management/SKILL.md +80 -0
- package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
- package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
- package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
- package/.aether/skills/colony/state-safety/SKILL.md +84 -0
- package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
- package/.aether/skills/domain/.manifest.json +24 -0
- package/.aether/skills/domain/README.md +33 -0
- package/.aether/skills/domain/django/SKILL.md +49 -0
- package/.aether/skills/domain/docker/SKILL.md +52 -0
- package/.aether/skills/domain/golang/SKILL.md +52 -0
- package/.aether/skills/domain/graphql/SKILL.md +51 -0
- package/.aether/skills/domain/html-css/SKILL.md +48 -0
- package/.aether/skills/domain/nextjs/SKILL.md +45 -0
- package/.aether/skills/domain/nodejs/SKILL.md +53 -0
- package/.aether/skills/domain/postgresql/SKILL.md +53 -0
- package/.aether/skills/domain/prisma/SKILL.md +59 -0
- package/.aether/skills/domain/python/SKILL.md +50 -0
- package/.aether/skills/domain/rails/SKILL.md +52 -0
- package/.aether/skills/domain/react/SKILL.md +45 -0
- package/.aether/skills/domain/rest-api/SKILL.md +58 -0
- package/.aether/skills/domain/svelte/SKILL.md +47 -0
- package/.aether/skills/domain/tailwind/SKILL.md +45 -0
- package/.aether/skills/domain/testing/SKILL.md +53 -0
- package/.aether/skills/domain/typescript/SKILL.md +58 -0
- package/.aether/skills/domain/vue/SKILL.md +49 -0
- package/.aether/templates/QUEEN.md.template +23 -41
- package/.aether/templates/colony-state-reset.jq.template +1 -0
- package/.aether/templates/colony-state.template.json +4 -0
- package/.aether/templates/learning-observations.template.json +6 -0
- package/.aether/templates/midden.template.json +13 -0
- package/.aether/templates/pheromones.template.json +6 -0
- package/.aether/templates/session.template.json +9 -0
- package/.aether/utils/atomic-write.sh +63 -17
- package/.aether/utils/chamber-utils.sh +145 -2
- package/.aether/utils/council.sh +425 -0
- package/.aether/utils/emoji-audit.sh +166 -0
- package/.aether/utils/error-handler.sh +21 -7
- package/.aether/utils/file-lock.sh +182 -27
- package/.aether/utils/flag.sh +278 -0
- package/.aether/utils/hive.sh +572 -0
- package/.aether/utils/immune.sh +508 -0
- package/.aether/utils/learning.sh +1928 -0
- package/.aether/utils/midden.sh +520 -0
- package/.aether/utils/oracle/oracle.md +168 -0
- package/.aether/utils/oracle/oracle.sh +1023 -0
- package/.aether/utils/pheromone.sh +2029 -0
- package/.aether/utils/queen.sh +1710 -0
- package/.aether/utils/scan.sh +860 -0
- package/.aether/utils/semantic-cli.sh +10 -8
- package/.aether/utils/session.sh +816 -0
- package/.aether/utils/skills.sh +509 -0
- package/.aether/utils/spawn-tree.sh +103 -271
- package/.aether/utils/spawn.sh +260 -0
- package/.aether/utils/state-api.sh +389 -0
- package/.aether/utils/state-loader.sh +8 -6
- package/.aether/utils/suggest.sh +611 -0
- package/.aether/utils/swarm-display.sh +10 -1
- package/.aether/utils/swarm.sh +1004 -0
- package/.aether/utils/watch-spawn-tree.sh +11 -2
- package/.aether/utils/xml-compose.sh +2 -2
- package/.aether/utils/xml-convert.sh +9 -5
- package/.aether/utils/xml-core.sh +5 -9
- package/.aether/utils/xml-query.sh +4 -4
- package/.aether/workers.md +86 -67
- package/.claude/agents/ant/aether-ambassador.md +2 -1
- package/.claude/agents/ant/aether-archaeologist.md +6 -1
- package/.claude/agents/ant/aether-architect.md +236 -0
- package/.claude/agents/ant/aether-auditor.md +6 -1
- package/.claude/agents/ant/aether-builder.md +38 -1
- package/.claude/agents/ant/aether-chaos.md +2 -1
- package/.claude/agents/ant/aether-chronicler.md +1 -0
- package/.claude/agents/ant/aether-gatekeeper.md +6 -1
- package/.claude/agents/ant/aether-includer.md +1 -0
- package/.claude/agents/ant/aether-keeper.md +1 -0
- package/.claude/agents/ant/aether-measurer.md +6 -1
- package/.claude/agents/ant/aether-oracle.md +237 -0
- package/.claude/agents/ant/aether-probe.md +2 -1
- package/.claude/agents/ant/aether-queen.md +6 -1
- package/.claude/agents/ant/aether-route-setter.md +6 -1
- package/.claude/agents/ant/aether-sage.md +68 -3
- package/.claude/agents/ant/aether-scout.md +38 -1
- package/.claude/agents/ant/aether-surveyor-disciplines.md +2 -1
- package/.claude/agents/ant/aether-surveyor-nest.md +2 -1
- package/.claude/agents/ant/aether-surveyor-pathogens.md +2 -1
- package/.claude/agents/ant/aether-surveyor-provisions.md +2 -1
- package/.claude/agents/ant/aether-tracker.md +6 -1
- package/.claude/agents/ant/aether-watcher.md +37 -1
- package/.claude/agents/ant/aether-weaver.md +2 -1
- package/.claude/commands/ant/archaeology.md +1 -8
- package/.claude/commands/ant/build.md +43 -1159
- package/.claude/commands/ant/chaos.md +1 -14
- package/.claude/commands/ant/colonize.md +3 -14
- package/.claude/commands/ant/continue.md +40 -1026
- package/.claude/commands/ant/council.md +213 -15
- package/.claude/commands/ant/data-clean.md +81 -0
- package/.claude/commands/ant/dream.md +12 -9
- package/.claude/commands/ant/entomb.md +62 -87
- package/.claude/commands/ant/export-signals.md +57 -0
- package/.claude/commands/ant/feedback.md +18 -0
- package/.claude/commands/ant/flag.md +12 -0
- package/.claude/commands/ant/flags.md +22 -8
- package/.claude/commands/ant/focus.md +18 -0
- package/.claude/commands/ant/help.md +40 -8
- package/.claude/commands/ant/history.md +3 -0
- package/.claude/commands/ant/import-signals.md +71 -0
- package/.claude/commands/ant/init.md +349 -191
- package/.claude/commands/ant/insert-phase.md +105 -0
- package/.claude/commands/ant/interpret.md +11 -0
- package/.claude/commands/ant/lay-eggs.md +167 -158
- package/.claude/commands/ant/maturity.md +22 -11
- package/.claude/commands/ant/memory-details.md +77 -0
- package/.claude/commands/ant/migrate-state.md +6 -0
- package/.claude/commands/ant/oracle.md +317 -62
- package/.claude/commands/ant/organize.md +10 -5
- package/.claude/commands/ant/patrol.md +620 -0
- package/.claude/commands/ant/pause-colony.md +8 -22
- package/.claude/commands/ant/phase.md +26 -37
- package/.claude/commands/ant/pheromones.md +156 -0
- package/.claude/commands/ant/plan.md +199 -50
- package/.claude/commands/ant/preferences.md +65 -0
- package/.claude/commands/ant/quick.md +100 -0
- package/.claude/commands/ant/redirect.md +18 -0
- package/.claude/commands/ant/resume-colony.md +37 -22
- package/.claude/commands/ant/resume.md +60 -7
- package/.claude/commands/ant/run.md +231 -0
- package/.claude/commands/ant/seal.md +506 -78
- package/.claude/commands/ant/skill-create.md +286 -0
- package/.claude/commands/ant/status.md +171 -1
- package/.claude/commands/ant/swarm.md +11 -23
- package/.claude/commands/ant/tunnels.md +1 -0
- package/.claude/commands/ant/update.md +58 -135
- package/.claude/commands/ant/verify-castes.md +90 -42
- package/.claude/commands/ant/watch.md +1 -0
- package/.opencode/agents/aether-ambassador.md +1 -1
- package/.opencode/agents/aether-architect.md +133 -0
- package/.opencode/agents/aether-builder.md +3 -3
- package/.opencode/agents/aether-oracle.md +137 -0
- package/.opencode/agents/aether-queen.md +1 -1
- package/.opencode/agents/aether-route-setter.md +1 -1
- package/.opencode/agents/aether-scout.md +1 -1
- package/.opencode/agents/aether-surveyor-disciplines.md +6 -1
- package/.opencode/agents/aether-surveyor-nest.md +6 -1
- package/.opencode/agents/aether-surveyor-pathogens.md +6 -1
- package/.opencode/agents/aether-surveyor-provisions.md +6 -1
- package/.opencode/agents/aether-tracker.md +1 -1
- package/.opencode/agents/aether-watcher.md +1 -1
- package/.opencode/agents/aether-weaver.md +1 -1
- package/.opencode/commands/ant/archaeology.md +7 -14
- package/.opencode/commands/ant/build.md +54 -88
- package/.opencode/commands/ant/chaos.md +7 -24
- package/.opencode/commands/ant/colonize.md +10 -17
- package/.opencode/commands/ant/continue.md +595 -66
- package/.opencode/commands/ant/council.md +150 -18
- package/.opencode/commands/ant/data-clean.md +77 -0
- package/.opencode/commands/ant/dream.md +15 -17
- package/.opencode/commands/ant/entomb.md +28 -18
- package/.opencode/commands/ant/export-signals.md +54 -0
- package/.opencode/commands/ant/feedback.md +24 -5
- package/.opencode/commands/ant/flag.md +16 -4
- package/.opencode/commands/ant/flags.md +24 -10
- package/.opencode/commands/ant/focus.md +22 -5
- package/.opencode/commands/ant/help.md +41 -8
- package/.opencode/commands/ant/history.md +9 -0
- package/.opencode/commands/ant/import-signals.md +68 -0
- package/.opencode/commands/ant/init.md +396 -154
- package/.opencode/commands/ant/insert-phase.md +111 -0
- package/.opencode/commands/ant/interpret.md +16 -0
- package/.opencode/commands/ant/lay-eggs.md +184 -112
- package/.opencode/commands/ant/maturity.md +18 -2
- package/.opencode/commands/ant/memory-details.md +83 -0
- package/.opencode/commands/ant/migrate-state.md +12 -0
- package/.opencode/commands/ant/oracle.md +322 -67
- package/.opencode/commands/ant/organize.md +14 -12
- package/.opencode/commands/ant/patrol.md +626 -0
- package/.opencode/commands/ant/pause-colony.md +12 -29
- package/.opencode/commands/ant/phase.md +30 -40
- package/.opencode/commands/ant/pheromones.md +162 -0
- package/.opencode/commands/ant/plan.md +210 -57
- package/.opencode/commands/ant/preferences.md +71 -0
- package/.opencode/commands/ant/quick.md +91 -0
- package/.opencode/commands/ant/redirect.md +22 -5
- package/.opencode/commands/ant/resume-colony.md +41 -29
- package/.opencode/commands/ant/resume.md +80 -20
- package/.opencode/commands/ant/run.md +237 -0
- package/.opencode/commands/ant/seal.md +230 -25
- package/.opencode/commands/ant/skill-create.md +63 -0
- package/.opencode/commands/ant/status.md +125 -30
- package/.opencode/commands/ant/swarm.md +3 -345
- package/.opencode/commands/ant/tunnels.md +3 -9
- package/.opencode/commands/ant/update.md +63 -127
- package/.opencode/commands/ant/verify-castes.md +96 -42
- package/.opencode/commands/ant/watch.md +7 -0
- package/CHANGELOG.md +368 -1
- package/README.md +195 -324
- package/bin/cli.js +236 -429
- package/bin/generate-commands.js +186 -0
- package/bin/generate-commands.sh +128 -89
- package/bin/lib/spawn-logger.js +0 -15
- package/bin/lib/update-transaction.js +285 -35
- package/bin/npx-install.js +178 -0
- package/bin/validate-package.sh +85 -3
- package/package.json +16 -4
- package/.aether/CONTEXT.md +0 -160
- package/.aether/docs/QUEEN.md +0 -84
- package/.aether/exchange/colony-registry.xml +0 -11
- package/.aether/exchange/pheromones.xml +0 -87
- package/.aether/exchange/queen-wisdom.xml +0 -14
- package/.aether/model-profiles.yaml +0 -100
- package/.aether/utils/spawn-with-model.sh +0 -56
- package/bin/lib/model-profiles.js +0 -445
- package/bin/lib/model-verify.js +0 -288
- package/bin/lib/proxy-health.js +0 -253
- package/bin/lib/telemetry.js +0 -441
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Hive Brain utility functions — extracted from aether-utils.sh
|
|
3
|
+
# Provides: _hive_init, _hive_store, _hive_read, _hive_abstract, _hive_promote
|
|
4
|
+
#
|
|
5
|
+
# These functions are sourced by aether-utils.sh at startup.
|
|
6
|
+
# All shared infrastructure (json_ok, json_err, atomic_write, acquire_lock,
|
|
7
|
+
# release_lock, LOCK_DIR, DATA_DIR, SCRIPT_DIR, error constants) is available.
|
|
8
|
+
#
|
|
9
|
+
# Lock handling: hive-store, hive-read, hive-init use acquire_lock_at/release_lock_at
|
|
10
|
+
# with colony-tagged lock files for cross-repo mutual exclusion on hub-level resources.
|
|
11
|
+
# This avoids global LOCK_DIR mutation (previous approach).
|
|
12
|
+
|
|
13
|
+
_hive_init() {
|
|
14
|
+
# Initialize the ~/.aether/hive/ directory and wisdom.json schema
|
|
15
|
+
# Usage: hive-init
|
|
16
|
+
# Idempotent: safe to call multiple times — will NOT overwrite existing wisdom.json
|
|
17
|
+
|
|
18
|
+
hv_hive_dir="$HOME/.aether/hive"
|
|
19
|
+
hv_wisdom_file="$hv_hive_dir/wisdom.json"
|
|
20
|
+
hv_already_existed="false"
|
|
21
|
+
|
|
22
|
+
mkdir -p "$hv_hive_dir"
|
|
23
|
+
|
|
24
|
+
if [[ -f "$hv_wisdom_file" ]]; then
|
|
25
|
+
hv_already_existed="true"
|
|
26
|
+
else
|
|
27
|
+
hv_created_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
28
|
+
hv_initial_schema=$(jq -n \
|
|
29
|
+
--arg created_at "$hv_created_at" \
|
|
30
|
+
--arg last_updated "$hv_created_at" \
|
|
31
|
+
'{
|
|
32
|
+
version: "1.0.0",
|
|
33
|
+
created_at: $created_at,
|
|
34
|
+
last_updated: $last_updated,
|
|
35
|
+
entries: [],
|
|
36
|
+
metadata: {
|
|
37
|
+
total_entries: 0,
|
|
38
|
+
max_entries: 200,
|
|
39
|
+
contributing_repos: []
|
|
40
|
+
}
|
|
41
|
+
}')
|
|
42
|
+
|
|
43
|
+
hv_lock_held=false
|
|
44
|
+
hv_lock_file=""
|
|
45
|
+
if type acquire_lock_at &>/dev/null; then
|
|
46
|
+
hv_colony_tag=$(bash "$0" colony-name 2>/dev/null | jq -r '.result.name // ""' | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//') || true
|
|
47
|
+
[[ -z "$hv_colony_tag" ]] && hv_colony_tag="unknown"
|
|
48
|
+
acquire_lock_at "$hv_wisdom_file" "$hv_hive_dir" "$hv_colony_tag" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on wisdom.json"
|
|
49
|
+
hv_lock_file="$LOCK_AT_FILE"
|
|
50
|
+
hv_lock_held=true
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
atomic_write "$hv_wisdom_file" "$hv_initial_schema" || {
|
|
54
|
+
[[ "$hv_lock_held" == "true" ]] && { release_lock_at "$hv_lock_file" 2>/dev/null || true; hv_lock_held=false; }
|
|
55
|
+
json_err "$E_JSON_INVALID" "Failed to write wisdom.json"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[[ "$hv_lock_held" == "true" ]] && { release_lock_at "$hv_lock_file" 2>/dev/null || true; hv_lock_held=false; }
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
json_ok "$(jq -n --arg dir "$hv_hive_dir" --argjson already_existed "$hv_already_existed" '{dir: $dir, initialized: true, already_existed: $already_existed}')"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_hive_store() {
|
|
65
|
+
# Store a wisdom entry in ~/.aether/hive/wisdom.json
|
|
66
|
+
# Usage: hive-store --text <text> --domain <csv> --source-repo <path> --confidence <0-1> --category <cat>
|
|
67
|
+
# Deduplicates by content hash. Same-repo dups skipped, cross-repo dups merged.
|
|
68
|
+
# Enforces 200 entry cap — evicts oldest by last_accessed when full.
|
|
69
|
+
|
|
70
|
+
hs_text=""
|
|
71
|
+
hs_domain=""
|
|
72
|
+
hs_source_repo=""
|
|
73
|
+
hs_confidence="0.5"
|
|
74
|
+
hs_category="general"
|
|
75
|
+
|
|
76
|
+
while [[ $# -gt 0 ]]; do
|
|
77
|
+
case "$1" in
|
|
78
|
+
--text) hs_text="${2:-}"; shift 2 ;;
|
|
79
|
+
--domain) hs_domain="${2:-}"; shift 2 ;;
|
|
80
|
+
--source-repo) hs_source_repo="${2:-}"; shift 2 ;;
|
|
81
|
+
--confidence) hs_confidence="${2:-0.5}"; shift 2 ;;
|
|
82
|
+
--category) hs_category="${2:-general}"; shift 2 ;;
|
|
83
|
+
*) shift ;;
|
|
84
|
+
esac
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
# Validate required fields
|
|
88
|
+
[[ -z "$hs_text" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --text argument" '{"missing":"text"}'
|
|
89
|
+
[[ -z "$hs_source_repo" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --source-repo argument" '{"missing":"source_repo"}'
|
|
90
|
+
|
|
91
|
+
# Validate confidence range
|
|
92
|
+
if ! [[ "$hs_confidence" =~ ^(0(\.[0-9]+)?|1(\.0+)?)$ ]]; then
|
|
93
|
+
json_err "$E_VALIDATION_FAILED" "Confidence must be a number between 0.0 and 1.0" "{\"provided\":\"$hs_confidence\"}"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Content sanitization (matches pheromone-write pattern)
|
|
97
|
+
if echo "$hs_text" | grep -Eiq '<[[:space:]]*/?(system|prompt|instructions|system-reminder|assistant|user|human)'; then
|
|
98
|
+
json_err "$E_VALIDATION_FAILED" "Wisdom content rejected: XML tag injection pattern detected"
|
|
99
|
+
fi
|
|
100
|
+
hs_text="${hs_text//</<}"
|
|
101
|
+
hs_text="${hs_text//>/>}"
|
|
102
|
+
hs_text="${hs_text:0:500}"
|
|
103
|
+
if echo "$hs_text" | grep -Eiq '(\$\(|`|(^|[[:space:]])curl([[:space:]]|$)|(^|[[:space:]])wget([[:space:]]|$)|(^|[[:space:]])rm([[:space:]]|$))'; then
|
|
104
|
+
json_err "$E_VALIDATION_FAILED" "Wisdom content rejected: potential injection pattern"
|
|
105
|
+
fi
|
|
106
|
+
if echo "$hs_text" | grep -Eiq '(ignore\s+(all\s+)?(previous\s+|prior\s+|above\s+)?instructions|disregard\s+(above|previous|all)|you are now |new instructions:|system prompt)'; then
|
|
107
|
+
json_err "$E_VALIDATION_FAILED" "Wisdom content rejected: prompt injection pattern detected"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Ensure hive is initialized
|
|
111
|
+
bash "$0" hive-init >/dev/null 2>&1 || json_err "$E_FILE_NOT_FOUND" "Unable to initialize hive"
|
|
112
|
+
|
|
113
|
+
hs_wisdom_file="$HOME/.aether/hive/wisdom.json"
|
|
114
|
+
[[ -f "$hs_wisdom_file" ]] || json_err "$E_FILE_NOT_FOUND" "Hive wisdom file not found"
|
|
115
|
+
|
|
116
|
+
if ! jq -e . "$hs_wisdom_file" >/dev/null 2>&1; then
|
|
117
|
+
json_err "$E_JSON_INVALID" "Hive wisdom JSON is invalid"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Generate content hash (first 12 chars of SHA-256)
|
|
121
|
+
hs_content_hash=$(printf '%s' "$hs_text" | shasum -a 256 | cut -c1-12)
|
|
122
|
+
|
|
123
|
+
# Parse domain tags CSV into JSON array
|
|
124
|
+
hs_domain_json="[]"
|
|
125
|
+
if [[ -n "$hs_domain" ]]; then
|
|
126
|
+
hs_domain_json=$(echo "$hs_domain" | tr ',' '\n' | jq -R 'gsub("^\\s+|\\s+$";"")' | jq -s '.')
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
hs_now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
130
|
+
|
|
131
|
+
# Acquire lock — use colony-tagged lock in hub dir for cross-repo mutual exclusion
|
|
132
|
+
hs_lock_held=false
|
|
133
|
+
hs_lock_file=""
|
|
134
|
+
if type acquire_lock_at &>/dev/null; then
|
|
135
|
+
hs_colony_tag=$(bash "$0" colony-name 2>/dev/null | jq -r '.result.name // ""' | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//') || true
|
|
136
|
+
[[ -z "$hs_colony_tag" ]] && hs_colony_tag="unknown"
|
|
137
|
+
acquire_lock_at "$hs_wisdom_file" "$HOME/.aether/hive" "$hs_colony_tag" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on wisdom.json"
|
|
138
|
+
hs_lock_file="$LOCK_AT_FILE"
|
|
139
|
+
hs_lock_held=true
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Check for existing entry with same content hash
|
|
143
|
+
hs_existing_idx=$(jq --arg hash "$hs_content_hash" '
|
|
144
|
+
.entries | to_entries | map(select(.value.id == $hash)) | .[0].key // -1
|
|
145
|
+
' "$hs_wisdom_file" 2>/dev/null)
|
|
146
|
+
|
|
147
|
+
if [[ "$hs_existing_idx" != "-1" ]] && [[ "$hs_existing_idx" != "null" ]] && [[ -n "$hs_existing_idx" ]]; then
|
|
148
|
+
# Entry exists — check if same repo
|
|
149
|
+
hs_has_repo=$(jq --arg hash "$hs_content_hash" --arg repo "$hs_source_repo" '
|
|
150
|
+
.entries[] | select(.id == $hash) | .source_repos | map(select(. == $repo)) | length > 0
|
|
151
|
+
' "$hs_wisdom_file" 2>/dev/null)
|
|
152
|
+
|
|
153
|
+
if [[ "$hs_has_repo" == "true" ]]; then
|
|
154
|
+
# Same repo duplicate — skip
|
|
155
|
+
[[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
|
|
156
|
+
json_ok "$(jq -n --arg id "$hs_content_hash" \
|
|
157
|
+
'{action: "skipped", reason: "duplicate from same repo", id: $id}')"
|
|
158
|
+
else
|
|
159
|
+
# Different repo — merge: increment validated_count, add repo, boost confidence
|
|
160
|
+
hs_updated=$(jq --arg hash "$hs_content_hash" \
|
|
161
|
+
--arg repo "$hs_source_repo" \
|
|
162
|
+
--arg now "$hs_now_iso" '
|
|
163
|
+
.entries = [.entries[] |
|
|
164
|
+
if .id == $hash then
|
|
165
|
+
.validated_count = (.validated_count + 1) |
|
|
166
|
+
.source_repos = (.source_repos + [$repo] | unique) |
|
|
167
|
+
.last_accessed = $now |
|
|
168
|
+
# Confidence boosting: tier based on source_repos count
|
|
169
|
+
# 2 repos -> 0.7, 3 repos -> 0.85, 4+ repos -> 0.95
|
|
170
|
+
# Never downgrade: use max(current, tier)
|
|
171
|
+
(.source_repos | length) as $repo_count |
|
|
172
|
+
(if $repo_count >= 4 then 0.95
|
|
173
|
+
elif $repo_count == 3 then 0.85
|
|
174
|
+
elif $repo_count == 2 then 0.7
|
|
175
|
+
else .confidence end) as $tier_confidence |
|
|
176
|
+
.confidence = ([.confidence, $tier_confidence] | max)
|
|
177
|
+
else . end
|
|
178
|
+
] |
|
|
179
|
+
.metadata.contributing_repos = ([.entries[].source_repos[]] | unique) |
|
|
180
|
+
.last_updated = $now
|
|
181
|
+
' "$hs_wisdom_file" 2>/dev/null) || {
|
|
182
|
+
[[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
|
|
183
|
+
json_err "$E_JSON_INVALID" "Failed to merge wisdom entry"
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
atomic_write "$hs_wisdom_file" "$hs_updated" || {
|
|
187
|
+
[[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
|
|
188
|
+
json_err "$E_JSON_INVALID" "Failed to write merged wisdom entry"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
[[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
|
|
192
|
+
hs_new_count=$(echo "$hs_updated" | jq --arg hash "$hs_content_hash" '.entries[] | select(.id == $hash) | .validated_count')
|
|
193
|
+
hs_new_confidence=$(echo "$hs_updated" | jq --arg hash "$hs_content_hash" '.entries[] | select(.id == $hash) | .confidence')
|
|
194
|
+
json_ok "$(jq -n --arg id "$hs_content_hash" --argjson validated_count "$hs_new_count" \
|
|
195
|
+
--argjson confidence "$hs_new_confidence" \
|
|
196
|
+
'{action: "merged", id: $id, validated_count: $validated_count, confidence: $confidence}')"
|
|
197
|
+
fi
|
|
198
|
+
else
|
|
199
|
+
# New entry — build and append
|
|
200
|
+
hs_entry=$(jq -n \
|
|
201
|
+
--arg id "$hs_content_hash" \
|
|
202
|
+
--arg text "$hs_text" \
|
|
203
|
+
--arg category "$hs_category" \
|
|
204
|
+
--argjson confidence "$hs_confidence" \
|
|
205
|
+
--argjson domain_tags "$hs_domain_json" \
|
|
206
|
+
--arg source_repo "$hs_source_repo" \
|
|
207
|
+
--arg created_at "$hs_now_iso" \
|
|
208
|
+
--arg last_accessed "$hs_now_iso" \
|
|
209
|
+
'{
|
|
210
|
+
id: $id,
|
|
211
|
+
text: $text,
|
|
212
|
+
category: $category,
|
|
213
|
+
confidence: $confidence,
|
|
214
|
+
domain_tags: $domain_tags,
|
|
215
|
+
source_repos: [$source_repo],
|
|
216
|
+
validated_count: 1,
|
|
217
|
+
created_at: $created_at,
|
|
218
|
+
last_accessed: $last_accessed,
|
|
219
|
+
access_count: 0
|
|
220
|
+
}')
|
|
221
|
+
|
|
222
|
+
# Append entry and enforce 200 cap (evict oldest by last_accessed)
|
|
223
|
+
hs_updated=$(jq --argjson entry "$hs_entry" --arg now "$hs_now_iso" '
|
|
224
|
+
.entries = (.entries + [$entry]) |
|
|
225
|
+
if (.entries | length) > 200 then
|
|
226
|
+
.entries = (.entries | sort_by(.last_accessed) | .[-200:])
|
|
227
|
+
else . end |
|
|
228
|
+
.metadata.total_entries = (.entries | length) |
|
|
229
|
+
.metadata.contributing_repos = ([.entries[].source_repos[]] | unique) |
|
|
230
|
+
.last_updated = $now
|
|
231
|
+
' "$hs_wisdom_file" 2>/dev/null) || {
|
|
232
|
+
[[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
|
|
233
|
+
json_err "$E_JSON_INVALID" "Failed to append wisdom entry"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
atomic_write "$hs_wisdom_file" "$hs_updated" || {
|
|
237
|
+
[[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
|
|
238
|
+
json_err "$E_JSON_INVALID" "Failed to write new wisdom entry"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
[[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
|
|
242
|
+
json_ok "$(jq -n --arg id "$hs_content_hash" --arg category "$hs_category" \
|
|
243
|
+
'{action: "stored", id: $id, category: $category}')"
|
|
244
|
+
fi
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_hive_read() {
|
|
248
|
+
# Read wisdom entries from ~/.aether/hive/wisdom.json with filtering and access tracking
|
|
249
|
+
# Usage: hive-read [--domain <csv>] [--limit <N>] [--min-confidence <0.0-1.0>] [--format <json|text>]
|
|
250
|
+
# Increments access_count and updates last_accessed for returned entries.
|
|
251
|
+
|
|
252
|
+
hr_domain=""
|
|
253
|
+
hr_limit="10"
|
|
254
|
+
hr_min_confidence="0.0"
|
|
255
|
+
hr_format="json"
|
|
256
|
+
|
|
257
|
+
while [[ $# -gt 0 ]]; do
|
|
258
|
+
case "$1" in
|
|
259
|
+
--domain) hr_domain="${2:-}"; shift 2 ;;
|
|
260
|
+
--limit) hr_limit="${2:-10}"; shift 2 ;;
|
|
261
|
+
--min-confidence) hr_min_confidence="${2:-0.0}"; shift 2 ;;
|
|
262
|
+
--format) hr_format="${2:-json}"; shift 2 ;;
|
|
263
|
+
*) shift ;;
|
|
264
|
+
esac
|
|
265
|
+
done
|
|
266
|
+
|
|
267
|
+
# Validate limit is a positive integer
|
|
268
|
+
if ! [[ "$hr_limit" =~ ^[0-9]+$ ]] || [[ "$hr_limit" -lt 1 ]]; then
|
|
269
|
+
json_err "$E_VALIDATION_FAILED" "Limit must be a positive integer" "{\"provided\":\"$hr_limit\"}"
|
|
270
|
+
fi
|
|
271
|
+
|
|
272
|
+
# Validate min-confidence is a valid number 0.0-1.0
|
|
273
|
+
if ! [[ "$hr_min_confidence" =~ ^(0(\.[0-9]+)?|1(\.0+)?)$ ]]; then
|
|
274
|
+
json_err "$E_VALIDATION_FAILED" "Min-confidence must be a number between 0.0 and 1.0" "{\"provided\":\"$hr_min_confidence\"}"
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Validate format
|
|
278
|
+
if [[ "$hr_format" != "json" ]] && [[ "$hr_format" != "text" ]]; then
|
|
279
|
+
json_err "$E_VALIDATION_FAILED" "Format must be 'json' or 'text'" "{\"provided\":\"$hr_format\"}"
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
hr_wisdom_file="$HOME/.aether/hive/wisdom.json"
|
|
283
|
+
|
|
284
|
+
# Fallback: no wisdom file
|
|
285
|
+
if [[ ! -f "$hr_wisdom_file" ]]; then
|
|
286
|
+
json_ok '{"entries":[],"total_matched":0,"fallback":"no_hive"}'
|
|
287
|
+
exit 0
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
# Validate JSON
|
|
291
|
+
if ! jq -e . "$hr_wisdom_file" >/dev/null 2>&1; then
|
|
292
|
+
json_ok '{"entries":[],"total_matched":0,"fallback":"invalid_json"}'
|
|
293
|
+
exit 0
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
# Parse domain tags CSV into JSON array for jq filtering
|
|
297
|
+
hr_domain_json="[]"
|
|
298
|
+
if [[ -n "$hr_domain" ]]; then
|
|
299
|
+
hr_domain_json=$(echo "$hr_domain" | tr ',' '\n' | jq -R 'gsub("^\\s+|\\s+$";"")' | jq -s '.')
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
# Filter, sort, and select entries using jq
|
|
303
|
+
hr_filtered=$(jq \
|
|
304
|
+
--argjson domain_filter "$hr_domain_json" \
|
|
305
|
+
--argjson min_conf "$hr_min_confidence" \
|
|
306
|
+
--argjson limit "$hr_limit" '
|
|
307
|
+
.entries
|
|
308
|
+
| map(
|
|
309
|
+
select(((.confidence // 0) | tonumber) >= $min_conf)
|
|
310
|
+
| if ($domain_filter | length) > 0 then
|
|
311
|
+
select(
|
|
312
|
+
[.domain_tags[] as $dt | $domain_filter[] | select(. == $dt)] | length > 0
|
|
313
|
+
)
|
|
314
|
+
else . end
|
|
315
|
+
)
|
|
316
|
+
| sort_by(-((.confidence // 0) | tonumber), -.validated_count)
|
|
317
|
+
| { total_matched: length, entries: .[:$limit], returned_ids: [.[:$limit][].id] }
|
|
318
|
+
' "$hr_wisdom_file" 2>/dev/null) || {
|
|
319
|
+
json_ok '{"entries":[],"total_matched":0,"fallback":"filter_error"}'
|
|
320
|
+
exit 0
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
hr_total_matched=$(echo "$hr_filtered" | jq -r '.total_matched')
|
|
324
|
+
hr_returned_ids=$(echo "$hr_filtered" | jq -c '.returned_ids')
|
|
325
|
+
hr_entries=$(echo "$hr_filtered" | jq -c '.entries')
|
|
326
|
+
|
|
327
|
+
# Update access_count and last_accessed for returned entries
|
|
328
|
+
if [[ "$hr_total_matched" -gt 0 ]] && [[ $(echo "$hr_returned_ids" | jq 'length') -gt 0 ]]; then
|
|
329
|
+
hr_now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
330
|
+
|
|
331
|
+
hr_lock_held=false
|
|
332
|
+
hr_lock_file=""
|
|
333
|
+
if type acquire_lock_at &>/dev/null; then
|
|
334
|
+
hr_colony_tag=$(bash "$0" colony-name 2>/dev/null | jq -r '.result.name // ""' | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//') || true
|
|
335
|
+
[[ -z "$hr_colony_tag" ]] && hr_colony_tag="unknown"
|
|
336
|
+
acquire_lock_at "$hr_wisdom_file" "$HOME/.aether/hive" "$hr_colony_tag" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on wisdom.json"
|
|
337
|
+
hr_lock_file="$LOCK_AT_FILE"
|
|
338
|
+
hr_lock_held=true
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
hr_updated=$(jq \
|
|
342
|
+
--argjson returned_ids "$hr_returned_ids" \
|
|
343
|
+
--arg now "$hr_now_iso" '
|
|
344
|
+
.entries = [.entries[] |
|
|
345
|
+
if (.id as $id | $returned_ids | index($id)) != null then
|
|
346
|
+
.access_count = ((.access_count // 0) + 1) |
|
|
347
|
+
.last_accessed = $now
|
|
348
|
+
else . end
|
|
349
|
+
] |
|
|
350
|
+
.last_updated = $now
|
|
351
|
+
' "$hr_wisdom_file" 2>/dev/null) || {
|
|
352
|
+
[[ "$hr_lock_held" == "true" ]] && { release_lock_at "$hr_lock_file" 2>/dev/null || true; hr_lock_held=false; }
|
|
353
|
+
json_err "$E_JSON_INVALID" "Failed to update access tracking in wisdom.json"
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
atomic_write "$hr_wisdom_file" "$hr_updated" || {
|
|
357
|
+
[[ "$hr_lock_held" == "true" ]] && { release_lock_at "$hr_lock_file" 2>/dev/null || true; hr_lock_held=false; }
|
|
358
|
+
json_err "$E_JSON_INVALID" "Failed to write updated wisdom.json"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
[[ "$hr_lock_held" == "true" ]] && { release_lock_at "$hr_lock_file" 2>/dev/null || true; hr_lock_held=false; }
|
|
362
|
+
fi
|
|
363
|
+
|
|
364
|
+
# Format output
|
|
365
|
+
if [[ "$hr_format" == "text" ]]; then
|
|
366
|
+
hr_text_output=$(echo "$hr_entries" | jq -r '
|
|
367
|
+
. as $entries |
|
|
368
|
+
if ($entries | length) == 0 then "(no wisdom entries)"
|
|
369
|
+
else
|
|
370
|
+
[range($entries | length)] |
|
|
371
|
+
map(
|
|
372
|
+
$entries[.] |
|
|
373
|
+
"[\(.confidence | tostring)] [\(.category)] \(.text) (validated: \(.validated_count), domains: \(.domain_tags | join(", ")))"
|
|
374
|
+
) | join("\n")
|
|
375
|
+
end
|
|
376
|
+
' 2>/dev/null)
|
|
377
|
+
|
|
378
|
+
hr_text_escaped=$(echo "$hr_text_output" | jq -Rs '.')
|
|
379
|
+
json_ok "$(jq -n --argjson entries "$hr_entries" --argjson total_matched "$hr_total_matched" \
|
|
380
|
+
--argjson text "$hr_text_escaped" \
|
|
381
|
+
'{entries: $entries, total_matched: $total_matched, text: $text}')"
|
|
382
|
+
else
|
|
383
|
+
json_ok "$(jq -n --argjson entries "$hr_entries" --argjson total_matched "$hr_total_matched" \
|
|
384
|
+
'{entries: $entries, total_matched: $total_matched}')"
|
|
385
|
+
fi
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
_hive_abstract() {
|
|
389
|
+
# Abstract a repo-specific instinct into generalized cross-colony wisdom
|
|
390
|
+
# Usage: hive-abstract --text <instinct_text> --source-repo <repo_path> [--domain <csv>]
|
|
391
|
+
# Returns: JSON with original text, abstracted text, and transformations applied.
|
|
392
|
+
# This is a TEXT TRANSFORMATION only — does NOT write to wisdom.json.
|
|
393
|
+
|
|
394
|
+
ha_text=""
|
|
395
|
+
ha_source_repo=""
|
|
396
|
+
ha_domain=""
|
|
397
|
+
|
|
398
|
+
while [[ $# -gt 0 ]]; do
|
|
399
|
+
case "$1" in
|
|
400
|
+
--text) ha_text="${2:-}"; shift 2 ;;
|
|
401
|
+
--source-repo) ha_source_repo="${2:-}"; shift 2 ;;
|
|
402
|
+
--domain) ha_domain="${2:-}"; shift 2 ;;
|
|
403
|
+
*) shift ;;
|
|
404
|
+
esac
|
|
405
|
+
done
|
|
406
|
+
|
|
407
|
+
# Validate required fields
|
|
408
|
+
[[ -z "$ha_text" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --text argument" '{"missing":"text"}'
|
|
409
|
+
[[ -z "$ha_source_repo" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --source-repo argument" '{"missing":"source_repo"}'
|
|
410
|
+
|
|
411
|
+
# Content sanitization (same pattern as hive-store)
|
|
412
|
+
if echo "$ha_text" | grep -Eiq '<[[:space:]]*/?(system|prompt|instructions|system-reminder|assistant|user|human)'; then
|
|
413
|
+
json_err "$E_VALIDATION_FAILED" "Content rejected: XML tag injection pattern detected"
|
|
414
|
+
fi
|
|
415
|
+
ha_text="${ha_text//</<}"
|
|
416
|
+
ha_text="${ha_text//>/>}"
|
|
417
|
+
ha_text="${ha_text:0:500}"
|
|
418
|
+
if echo "$ha_text" | grep -Eiq '(\$\(|`|(^|[[:space:]])curl([[:space:]]|$)|(^|[[:space:]])wget([[:space:]]|$)|(^|[[:space:]])rm([[:space:]]|$))'; then
|
|
419
|
+
json_err "$E_VALIDATION_FAILED" "Content rejected: potential injection pattern"
|
|
420
|
+
fi
|
|
421
|
+
if echo "$ha_text" | grep -Eiq '(ignore\s+(all\s+)?(previous\s+|prior\s+|above\s+)?instructions|disregard\s+(above|previous|all)|you are now |new instructions:|system prompt)'; then
|
|
422
|
+
json_err "$E_VALIDATION_FAILED" "Content rejected: prompt injection pattern detected"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
# Save original (post-sanitization) for output
|
|
426
|
+
ha_original="$ha_text"
|
|
427
|
+
|
|
428
|
+
# Track which transformations are applied
|
|
429
|
+
ha_transforms=()
|
|
430
|
+
|
|
431
|
+
# Extract repo basename for name stripping
|
|
432
|
+
ha_repo_basename=$(basename "$ha_source_repo")
|
|
433
|
+
|
|
434
|
+
# 1. Strip absolute file paths (match /path/to/file.ext patterns)
|
|
435
|
+
# Replace paths that start with / and contain at least 2 segments
|
|
436
|
+
ha_abstracted="$ha_text"
|
|
437
|
+
if echo "$ha_abstracted" | grep -qE '/[A-Za-z_][A-Za-z0-9_./-]*/[A-Za-z0-9_./-]+'; then
|
|
438
|
+
ha_abstracted=$(echo "$ha_abstracted" | sed -E 's|/[A-Za-z_][A-Za-z0-9_./-]*/[A-Za-z0-9_./-]+|<source-file>|g')
|
|
439
|
+
ha_transforms+=("path_strip")
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
# 2. Strip repo basename (case-sensitive match of the project name)
|
|
443
|
+
if [[ -n "$ha_repo_basename" ]] && echo "$ha_abstracted" | grep -qF "$ha_repo_basename"; then
|
|
444
|
+
ha_abstracted=$(echo "$ha_abstracted" | sed "s|${ha_repo_basename}|<project>|g")
|
|
445
|
+
ha_transforms+=("repo_name_strip")
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
# 3. Strip version numbers (v1.2.3, v2.0.0, etc.)
|
|
449
|
+
if echo "$ha_abstracted" | grep -qE 'v[0-9]+\.[0-9]+\.[0-9]+'; then
|
|
450
|
+
ha_abstracted=$(echo "$ha_abstracted" | sed -E 's/v[0-9]+\.[0-9]+\.[0-9]+/<version>/g')
|
|
451
|
+
ha_transforms+=("version_strip")
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
# 4. Strip branch names (feature/xyz, bugfix/abc, hotfix/def, release/xyz)
|
|
455
|
+
if echo "$ha_abstracted" | grep -qE '(feature|bugfix|hotfix|release)/[A-Za-z0-9_.-]+'; then
|
|
456
|
+
ha_abstracted=$(echo "$ha_abstracted" | sed -E 's/(feature|bugfix|hotfix|release)\/[A-Za-z0-9_.-]+/<branch>/g')
|
|
457
|
+
ha_transforms+=("branch_strip")
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
# Parse domain tags CSV into JSON array
|
|
461
|
+
ha_domain_json="[]"
|
|
462
|
+
if [[ -n "$ha_domain" ]]; then
|
|
463
|
+
ha_domain_json=$(echo "$ha_domain" | tr ',' '\n' | jq -R 'gsub("^\\s+|\\s+$";"")' | jq -s '.')
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
# Build transformations JSON array
|
|
467
|
+
ha_transforms_json="[]"
|
|
468
|
+
if [[ ${#ha_transforms[@]} -gt 0 ]]; then
|
|
469
|
+
ha_transforms_json=$(printf '%s\n' "${ha_transforms[@]}" | jq -R '.' | jq -s '.')
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
# Build result JSON using jq for proper escaping
|
|
473
|
+
ha_result=$(jq -n \
|
|
474
|
+
--arg original "$ha_original" \
|
|
475
|
+
--arg abstracted "$ha_abstracted" \
|
|
476
|
+
--arg source_repo "$ha_source_repo" \
|
|
477
|
+
--argjson domain_tags "$ha_domain_json" \
|
|
478
|
+
--argjson transformations "$ha_transforms_json" \
|
|
479
|
+
'{
|
|
480
|
+
original: $original,
|
|
481
|
+
abstracted: $abstracted,
|
|
482
|
+
source_repo: $source_repo,
|
|
483
|
+
domain_tags: $domain_tags,
|
|
484
|
+
transformations_applied: $transformations
|
|
485
|
+
}')
|
|
486
|
+
|
|
487
|
+
json_ok "$ha_result"
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
_hive_promote() {
|
|
491
|
+
# Orchestrate the full promotion pipeline: abstract an instinct, then store it
|
|
492
|
+
# Usage: hive-promote --text <instinct_text> --source-repo <repo_path> [--domain <csv>] [--confidence <0-1>] [--category <cat>]
|
|
493
|
+
# Calls hive-abstract internally to generalize the text, then hive-store to persist it.
|
|
494
|
+
# Returns: combined result with action mapping (stored->promoted, merged->merged, skipped->skipped).
|
|
495
|
+
|
|
496
|
+
hp_text=""
|
|
497
|
+
hp_source_repo=""
|
|
498
|
+
hp_domain=""
|
|
499
|
+
hp_confidence="0.7"
|
|
500
|
+
hp_category="pattern"
|
|
501
|
+
|
|
502
|
+
while [[ $# -gt 0 ]]; do
|
|
503
|
+
case "$1" in
|
|
504
|
+
--text) hp_text="${2:-}"; shift 2 ;;
|
|
505
|
+
--source-repo) hp_source_repo="${2:-}"; shift 2 ;;
|
|
506
|
+
--domain) hp_domain="${2:-}"; shift 2 ;;
|
|
507
|
+
--confidence) hp_confidence="${2:-0.7}"; shift 2 ;;
|
|
508
|
+
--category) hp_category="${2:-pattern}"; shift 2 ;;
|
|
509
|
+
*) shift ;;
|
|
510
|
+
esac
|
|
511
|
+
done
|
|
512
|
+
|
|
513
|
+
# Validate required fields
|
|
514
|
+
[[ -z "$hp_text" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --text argument" '{"missing":"text"}'
|
|
515
|
+
[[ -z "$hp_source_repo" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --source-repo argument" '{"missing":"source_repo"}'
|
|
516
|
+
|
|
517
|
+
# Ensure hive is initialized (idempotent)
|
|
518
|
+
bash "$0" hive-init >/dev/null 2>&1 || json_err "$E_FILE_NOT_FOUND" "Unable to initialize hive"
|
|
519
|
+
|
|
520
|
+
# Step 1: Abstract the instinct text
|
|
521
|
+
hp_abstract_args=(hive-abstract --text "$hp_text" --source-repo "$hp_source_repo")
|
|
522
|
+
[[ -n "$hp_domain" ]] && hp_abstract_args+=(--domain "$hp_domain")
|
|
523
|
+
|
|
524
|
+
hp_abstract_result=$(bash "$0" "${hp_abstract_args[@]}" 2>&1) || {
|
|
525
|
+
json_err "$E_VALIDATION_FAILED" "Abstraction failed: $(echo "$hp_abstract_result" | jq -r '.error.message // "unknown error"' 2>/dev/null)"
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
# Extract abstracted text and transformations from abstract result
|
|
529
|
+
hp_abstracted=$(echo "$hp_abstract_result" | jq -r '.result.abstracted // empty' 2>/dev/null)
|
|
530
|
+
hp_original=$(echo "$hp_abstract_result" | jq -r '.result.original // empty' 2>/dev/null)
|
|
531
|
+
hp_transforms=$(echo "$hp_abstract_result" | jq -c '.result.transformations_applied // []' 2>/dev/null)
|
|
532
|
+
|
|
533
|
+
if [[ -z "$hp_abstracted" ]]; then
|
|
534
|
+
json_err "$E_VALIDATION_FAILED" "Abstraction returned empty text"
|
|
535
|
+
fi
|
|
536
|
+
|
|
537
|
+
# Step 2: Store the abstracted text in the hive
|
|
538
|
+
hp_store_args=(hive-store --text "$hp_abstracted" --source-repo "$hp_source_repo" --confidence "$hp_confidence" --category "$hp_category")
|
|
539
|
+
[[ -n "$hp_domain" ]] && hp_store_args+=(--domain "$hp_domain")
|
|
540
|
+
|
|
541
|
+
hp_store_result=$(bash "$0" "${hp_store_args[@]}" 2>&1) || {
|
|
542
|
+
json_err "$E_VALIDATION_FAILED" "Store failed: $(echo "$hp_store_result" | jq -r '.error.message // "unknown error"' 2>/dev/null)"
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# Extract store action
|
|
546
|
+
hp_store_action=$(echo "$hp_store_result" | jq -r '.result.action // "unknown"' 2>/dev/null)
|
|
547
|
+
|
|
548
|
+
# Map store action to promote action: stored->promoted, merged->merged, skipped->skipped
|
|
549
|
+
hp_action="$hp_store_action"
|
|
550
|
+
[[ "$hp_store_action" == "stored" ]] && hp_action="promoted"
|
|
551
|
+
|
|
552
|
+
# Build combined result
|
|
553
|
+
hp_result=$(jq -n \
|
|
554
|
+
--arg action "$hp_action" \
|
|
555
|
+
--arg original "$hp_original" \
|
|
556
|
+
--arg abstracted "$hp_abstracted" \
|
|
557
|
+
--argjson transformations "$hp_transforms" \
|
|
558
|
+
--arg store_action "$hp_store_action" \
|
|
559
|
+
--argjson confidence "$hp_confidence" \
|
|
560
|
+
--arg source_repo "$hp_source_repo" \
|
|
561
|
+
'{
|
|
562
|
+
action: $action,
|
|
563
|
+
original: $original,
|
|
564
|
+
abstracted: $abstracted,
|
|
565
|
+
transformations: $transformations,
|
|
566
|
+
store_action: $store_action,
|
|
567
|
+
confidence: $confidence,
|
|
568
|
+
source_repo: $source_repo
|
|
569
|
+
}')
|
|
570
|
+
|
|
571
|
+
json_ok "$hp_result"
|
|
572
|
+
}
|