aether-colony 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aether/aether-utils.sh +3150 -3349
- 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 +438 -0
- package/.aether/commands/continue.yaml +1484 -0
- package/.aether/commands/council.yaml +304 -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 +469 -0
- package/.aether/commands/insert-phase.yaml +98 -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 +1313 -0
- package/.aether/commands/preferences.yaml +63 -0
- package/.aether/commands/redirect.yaml +123 -0
- package/.aether/commands/resume-colony.yaml +373 -0
- package/.aether/commands/resume.yaml +398 -0
- package/.aether/commands/run.yaml +193 -0
- package/.aether/commands/seal.yaml +1205 -0
- package/.aether/commands/skill-create.yaml +337 -0
- package/.aether/commands/status.yaml +364 -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 +1682 -0
- package/.aether/docs/command-playbooks/build-prep.md +283 -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 +1724 -0
- package/.aether/docs/command-playbooks/continue-gates.md +686 -0
- package/.aether/docs/command-playbooks/continue-verify.md +406 -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/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 +267 -0
- package/.aether/utils/hive.sh +572 -0
- package/.aether/utils/learning.sh +1928 -0
- package/.aether/utils/midden.sh +342 -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 +1698 -0
- package/.aether/utils/scan.sh +860 -0
- package/.aether/utils/semantic-cli.sh +10 -8
- package/.aether/utils/session.sh +552 -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 +199 -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 +1 -14
- package/.claude/commands/ant/continue.md +40 -1026
- package/.claude/commands/ant/council.md +9 -16
- 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 +316 -191
- package/.claude/commands/ant/insert-phase.md +101 -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 +175 -52
- package/.claude/commands/ant/preferences.md +65 -0
- package/.claude/commands/ant/redirect.md +18 -0
- package/.claude/commands/ant/resume-colony.md +34 -20
- package/.claude/commands/ant/resume.md +51 -7
- package/.claude/commands/ant/run.md +195 -0
- package/.claude/commands/ant/seal.md +497 -78
- package/.claude/commands/ant/skill-create.md +286 -0
- package/.claude/commands/ant/status.md +127 -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 +8 -17
- package/.opencode/commands/ant/continue.md +595 -66
- package/.opencode/commands/ant/council.md +11 -22
- 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 +365 -156
- package/.opencode/commands/ant/insert-phase.md +107 -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 +184 -56
- package/.opencode/commands/ant/preferences.md +71 -0
- package/.opencode/commands/ant/redirect.md +22 -5
- package/.opencode/commands/ant/resume-colony.md +38 -27
- package/.opencode/commands/ant/resume.md +71 -20
- package/.opencode/commands/ant/run.md +201 -0
- package/.opencode/commands/ant/seal.md +230 -25
- package/.opencode/commands/ant/skill-create.md +63 -0
- package/.opencode/commands/ant/status.md +124 -31
- 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 +278 -1
- package/README.md +188 -340
- 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 +7 -3
- 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,1004 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Swarm utility functions -- extracted from aether-utils.sh
|
|
3
|
+
# Provides: _autofix_checkpoint, _autofix_rollback, _swarm_findings_init, _swarm_findings_add,
|
|
4
|
+
# _swarm_findings_read, _swarm_solution_set, _swarm_cleanup, _swarm_activity_log,
|
|
5
|
+
# _swarm_display_init, _swarm_display_update, _swarm_display_get, _swarm_display_render,
|
|
6
|
+
# _swarm_display_inline, _swarm_display_text, _swarm_timing_start, _swarm_timing_get,
|
|
7
|
+
# _swarm_timing_eta
|
|
8
|
+
# Note: Uses get_caste_emoji() which remains in the main file (shared helper)
|
|
9
|
+
|
|
10
|
+
# ============================================================================
|
|
11
|
+
# _autofix_checkpoint
|
|
12
|
+
# Create checkpoint before applying auto-fix
|
|
13
|
+
# Usage: _autofix_checkpoint [label]
|
|
14
|
+
# Returns: {type: "stash"|"commit"|"none", ref: "..."}
|
|
15
|
+
# IMPORTANT: Only stash Aether-related files, never touch user work
|
|
16
|
+
# ============================================================================
|
|
17
|
+
_autofix_checkpoint() {
|
|
18
|
+
if git rev-parse --git-dir >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: may not be a git repo
|
|
19
|
+
# Check if there are changes to Aether-managed files only
|
|
20
|
+
# Target directories that Aether is allowed to modify
|
|
21
|
+
target_dirs=".aether .claude/commands/ant .claude/commands/st .opencode bin"
|
|
22
|
+
has_changes=false
|
|
23
|
+
|
|
24
|
+
for dir in $target_dirs; do
|
|
25
|
+
if [[ -d "$dir" ]] && [[ -n "$(git status --porcelain "$dir" 2>/dev/null)" ]]; then # SUPPRESS:OK -- existence-test: may not be a git repo
|
|
26
|
+
has_changes=true
|
|
27
|
+
break
|
|
28
|
+
fi
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
if [[ "$has_changes" == "true" ]]; then
|
|
32
|
+
label="${1:-autofix-$(date +%s)}"
|
|
33
|
+
stash_name="aether-checkpoint: $label"
|
|
34
|
+
# Only stash Aether-managed directories, never touch user files
|
|
35
|
+
if git stash push -m "$stash_name" -- $target_dirs >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: stash operation may fail
|
|
36
|
+
json_ok "$(jq -n --arg ref "$stash_name" '{type: "stash", ref: $ref}')"
|
|
37
|
+
else
|
|
38
|
+
# Stash failed (possibly due to conflicts), record commit hash
|
|
39
|
+
hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown") # SUPPRESS:OK -- read-default: may not have commits yet
|
|
40
|
+
json_ok "$(jq -n --arg ref "$hash" '{type: "commit", ref: $ref}')"
|
|
41
|
+
fi
|
|
42
|
+
else
|
|
43
|
+
# No changes in Aether-managed directories, just record commit hash
|
|
44
|
+
hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown") # SUPPRESS:OK -- read-default: may not have commits yet
|
|
45
|
+
json_ok "$(jq -n --arg ref "$hash" '{type: "commit", ref: $ref}')"
|
|
46
|
+
fi
|
|
47
|
+
else
|
|
48
|
+
json_ok '{"type":"none","ref":null}'
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ============================================================================
|
|
53
|
+
# _autofix_rollback
|
|
54
|
+
# Rollback from checkpoint if fix failed
|
|
55
|
+
# Usage: _autofix_rollback <type> <ref>
|
|
56
|
+
# Returns: {rolled_back: bool, method: "stash"|"reset"|"none"}
|
|
57
|
+
# ============================================================================
|
|
58
|
+
_autofix_rollback() {
|
|
59
|
+
ref_type="${1:-none}"
|
|
60
|
+
ref="${2:-}"
|
|
61
|
+
|
|
62
|
+
case "$ref_type" in
|
|
63
|
+
stash)
|
|
64
|
+
# Find and pop the stash
|
|
65
|
+
stash_ref=$(git stash list 2>/dev/null | grep "$ref" | head -1 | cut -d: -f1 || echo "") # SUPPRESS:OK -- existence-test: stash operation may fail
|
|
66
|
+
if [[ -n "$stash_ref" ]]; then
|
|
67
|
+
if git stash pop "$stash_ref" >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: stash operation may fail
|
|
68
|
+
json_ok '{"rolled_back":true,"method":"stash"}'
|
|
69
|
+
else
|
|
70
|
+
json_ok '{"rolled_back":false,"method":"stash","error":"stash pop failed"}'
|
|
71
|
+
fi
|
|
72
|
+
else
|
|
73
|
+
json_ok '{"rolled_back":false,"method":"stash","error":"stash not found"}'
|
|
74
|
+
fi
|
|
75
|
+
;;
|
|
76
|
+
commit)
|
|
77
|
+
# Reset to the commit
|
|
78
|
+
if [[ -n "$ref" && "$ref" != "unknown" ]]; then
|
|
79
|
+
if git reset --hard "$ref" >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: reset may fail
|
|
80
|
+
json_ok '{"rolled_back":true,"method":"reset"}'
|
|
81
|
+
else
|
|
82
|
+
json_ok '{"rolled_back":false,"method":"reset","error":"reset failed"}'
|
|
83
|
+
fi
|
|
84
|
+
else
|
|
85
|
+
json_ok '{"rolled_back":false,"method":"reset","error":"invalid ref"}'
|
|
86
|
+
fi
|
|
87
|
+
;;
|
|
88
|
+
none|*)
|
|
89
|
+
json_ok '{"rolled_back":false,"method":"none"}'
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# ============================================================================
|
|
95
|
+
# _swarm_findings_init
|
|
96
|
+
# Initialize swarm findings file
|
|
97
|
+
# Usage: _swarm_findings_init <swarm_id>
|
|
98
|
+
# ============================================================================
|
|
99
|
+
_swarm_findings_init() {
|
|
100
|
+
swarm_id="${1:-swarm-$(date +%s)}"
|
|
101
|
+
findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
|
|
102
|
+
|
|
103
|
+
mkdir -p "$COLONY_DATA_DIR"
|
|
104
|
+
# Build initial findings JSON safely via jq (swarm_id may contain JSON-special chars)
|
|
105
|
+
local init_json
|
|
106
|
+
init_json=$(jq -n --arg sid "$swarm_id" --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '{
|
|
107
|
+
swarm_id: $sid,
|
|
108
|
+
created_at: $ts,
|
|
109
|
+
status: "active",
|
|
110
|
+
findings: [],
|
|
111
|
+
solution: null
|
|
112
|
+
}')
|
|
113
|
+
echo "$init_json" > "$findings_file"
|
|
114
|
+
json_ok "$(jq -n --arg swarm_id "$swarm_id" --arg file "$findings_file" \
|
|
115
|
+
'{swarm_id: $swarm_id, file: $file}')"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# ============================================================================
|
|
119
|
+
# _swarm_findings_add
|
|
120
|
+
# Add a finding from a scout
|
|
121
|
+
# Usage: _swarm_findings_add <swarm_id> <scout_type> <confidence> <finding_json>
|
|
122
|
+
# ============================================================================
|
|
123
|
+
_swarm_findings_add() {
|
|
124
|
+
swarm_id="${1:-}"
|
|
125
|
+
scout_type="${2:-}"
|
|
126
|
+
confidence="${3:-0.5}"
|
|
127
|
+
finding="${4:-}"
|
|
128
|
+
|
|
129
|
+
[[ -z "$swarm_id" || -z "$scout_type" || -z "$finding" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-add <swarm_id> <scout_type> <confidence> <finding_json>"
|
|
130
|
+
|
|
131
|
+
findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
|
|
132
|
+
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
133
|
+
|
|
134
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
135
|
+
|
|
136
|
+
# Add finding to array
|
|
137
|
+
updated=$(jq --arg scout "$scout_type" --arg conf "$confidence" --arg ts "$ts" --argjson finding "$finding" '
|
|
138
|
+
.findings += [{
|
|
139
|
+
"scout": $scout,
|
|
140
|
+
"confidence": ($conf | tonumber),
|
|
141
|
+
"timestamp": $ts,
|
|
142
|
+
"finding": $finding
|
|
143
|
+
}]
|
|
144
|
+
' "$findings_file")
|
|
145
|
+
|
|
146
|
+
atomic_write "$findings_file" "$updated" || {
|
|
147
|
+
_aether_log_error "Could not save swarm findings"
|
|
148
|
+
json_err "$E_UNKNOWN" "Failed to write swarm findings file"
|
|
149
|
+
}
|
|
150
|
+
count=$(echo "$updated" | jq '.findings | length')
|
|
151
|
+
json_ok "$(jq -n --arg scout "$scout_type" --argjson total_findings "$count" \
|
|
152
|
+
'{added: true, scout: $scout, total_findings: $total_findings}')"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# ============================================================================
|
|
156
|
+
# _swarm_findings_read
|
|
157
|
+
# Read all findings for a swarm
|
|
158
|
+
# Usage: _swarm_findings_read <swarm_id>
|
|
159
|
+
# ============================================================================
|
|
160
|
+
_swarm_findings_read() {
|
|
161
|
+
swarm_id="${1:-}"
|
|
162
|
+
[[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-read <swarm_id>"
|
|
163
|
+
|
|
164
|
+
findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
|
|
165
|
+
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
166
|
+
|
|
167
|
+
json_ok "$(cat "$findings_file")"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# ============================================================================
|
|
171
|
+
# _swarm_solution_set
|
|
172
|
+
# Set the chosen solution for a swarm
|
|
173
|
+
# Usage: _swarm_solution_set <swarm_id> <solution_json>
|
|
174
|
+
# ============================================================================
|
|
175
|
+
_swarm_solution_set() {
|
|
176
|
+
swarm_id="${1:-}"
|
|
177
|
+
solution="${2:-}"
|
|
178
|
+
|
|
179
|
+
[[ -z "$swarm_id" || -z "$solution" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-solution-set <swarm_id> <solution_json>"
|
|
180
|
+
|
|
181
|
+
findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
|
|
182
|
+
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
183
|
+
|
|
184
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
185
|
+
|
|
186
|
+
updated=$(jq --argjson solution "$solution" --arg ts "$ts" '
|
|
187
|
+
.solution = $solution |
|
|
188
|
+
.status = "resolved" |
|
|
189
|
+
.resolved_at = $ts
|
|
190
|
+
' "$findings_file")
|
|
191
|
+
|
|
192
|
+
atomic_write "$findings_file" "$updated" || {
|
|
193
|
+
_aether_log_error "Could not save swarm solution"
|
|
194
|
+
json_err "$E_UNKNOWN" "Failed to write swarm findings file"
|
|
195
|
+
}
|
|
196
|
+
json_ok "$(jq -n --arg swarm_id "$swarm_id" '{solution_set: true, swarm_id: $swarm_id}')"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# ============================================================================
|
|
200
|
+
# _swarm_cleanup
|
|
201
|
+
# Clean up swarm files after completion
|
|
202
|
+
# Usage: _swarm_cleanup <swarm_id> [--archive]
|
|
203
|
+
# ============================================================================
|
|
204
|
+
_swarm_cleanup() {
|
|
205
|
+
swarm_id="${1:-}"
|
|
206
|
+
archive="${2:-}"
|
|
207
|
+
|
|
208
|
+
[[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-cleanup <swarm_id> [--archive]"
|
|
209
|
+
|
|
210
|
+
findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
|
|
211
|
+
|
|
212
|
+
if [[ -f "$findings_file" ]]; then
|
|
213
|
+
if [[ "$archive" == "--archive" ]]; then
|
|
214
|
+
mkdir -p "$COLONY_DATA_DIR/swarm-archive"
|
|
215
|
+
mv "$findings_file" "$COLONY_DATA_DIR/swarm-archive/"
|
|
216
|
+
json_ok "$(jq -n --arg swarm_id "$swarm_id" '{archived: true, swarm_id: $swarm_id}')"
|
|
217
|
+
else
|
|
218
|
+
rm -f "$findings_file"
|
|
219
|
+
json_ok "$(jq -n --arg swarm_id "$swarm_id" '{deleted: true, swarm_id: $swarm_id}')"
|
|
220
|
+
fi
|
|
221
|
+
else
|
|
222
|
+
json_ok "$(jq -n --arg swarm_id "$swarm_id" '{not_found: true, swarm_id: $swarm_id}')"
|
|
223
|
+
fi
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# ============================================================================
|
|
227
|
+
# _swarm_activity_log
|
|
228
|
+
# Log an activity entry for swarm visualization
|
|
229
|
+
# Usage: _swarm_activity_log <ant_name> <action> <details>
|
|
230
|
+
# ============================================================================
|
|
231
|
+
_swarm_activity_log() {
|
|
232
|
+
ant_name="${1:-}"
|
|
233
|
+
action="${2:-}"
|
|
234
|
+
details="${3:-}"
|
|
235
|
+
[[ -z "$ant_name" || -z "$action" || -z "$details" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-activity-log <ant_name> <action> <details>"
|
|
236
|
+
|
|
237
|
+
mkdir -p "$COLONY_DATA_DIR"
|
|
238
|
+
log_file="$COLONY_DATA_DIR/swarm-activity.log"
|
|
239
|
+
ts=$(date -u +"%H:%M:%S")
|
|
240
|
+
echo "[$ts] $ant_name: $action $details" >> "$log_file"
|
|
241
|
+
json_ok '"logged"'
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# ============================================================================
|
|
245
|
+
# _swarm_display_init
|
|
246
|
+
# Initialize swarm display state file
|
|
247
|
+
# Usage: _swarm_display_init <swarm_id>
|
|
248
|
+
# ============================================================================
|
|
249
|
+
_swarm_display_init() {
|
|
250
|
+
swarm_id="${1:-swarm-$(date +%s)}"
|
|
251
|
+
mkdir -p "$COLONY_DATA_DIR"
|
|
252
|
+
|
|
253
|
+
display_file="$COLONY_DATA_DIR/swarm-display.json"
|
|
254
|
+
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
255
|
+
|
|
256
|
+
# Build initial display JSON safely via jq (swarm_id may contain JSON-special chars)
|
|
257
|
+
local init_json
|
|
258
|
+
init_json=$(jq -n --arg sid "$swarm_id" --arg ts "$ts" \
|
|
259
|
+
--arg e1 "🍄" --arg e2 "🥚" --arg e3 "🗑️" --arg e4 "👑" --arg e5 "🌿" '{
|
|
260
|
+
swarm_id: $sid,
|
|
261
|
+
timestamp: $ts,
|
|
262
|
+
active_ants: [],
|
|
263
|
+
summary: { total_active: 0, by_caste: {}, by_zone: {} },
|
|
264
|
+
chambers: {
|
|
265
|
+
fungus_garden: {activity: 0, icon: $e1},
|
|
266
|
+
nursery: {activity: 0, icon: $e2},
|
|
267
|
+
refuse_pile: {activity: 0, icon: $e3},
|
|
268
|
+
throne_room: {activity: 0, icon: $e4},
|
|
269
|
+
foraging_trail: {activity: 0, icon: $e5}
|
|
270
|
+
}
|
|
271
|
+
}')
|
|
272
|
+
atomic_write "$display_file" "$init_json"
|
|
273
|
+
json_ok "$(jq -n --arg sid "$swarm_id" '{swarm_id: $sid, initialized: true}')"
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# ============================================================================
|
|
277
|
+
# _swarm_display_update
|
|
278
|
+
# Update ant activity in swarm display
|
|
279
|
+
# Usage: _swarm_display_update <ant_name> <caste> <ant_status> <task> [parent] [tools_json] [tokens] [chamber] [progress]
|
|
280
|
+
# ============================================================================
|
|
281
|
+
_swarm_display_update() {
|
|
282
|
+
ant_name="${1:-}"
|
|
283
|
+
caste="${2:-}"
|
|
284
|
+
ant_status="${3:-}"
|
|
285
|
+
task="${4:-}"
|
|
286
|
+
parent="${5:-}"
|
|
287
|
+
tools_json="${6:-}"
|
|
288
|
+
[[ -z "$tools_json" ]] && tools_json="{}"
|
|
289
|
+
tokens="${7:-0}"
|
|
290
|
+
chamber="${8:-}"
|
|
291
|
+
progress="${9:-0}"
|
|
292
|
+
|
|
293
|
+
[[ -z "$ant_name" || -z "$caste" || -z "$ant_status" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-display-update <ant_name> <caste> <ant_status> <task> [parent] [tools_json] [tokens] [chamber] [progress]"
|
|
294
|
+
|
|
295
|
+
# Tolerate malformed argument ordering from LLM-generated commands.
|
|
296
|
+
# Common failure mode: tools_json omitted, so tokens/chamber/progress shift left.
|
|
297
|
+
tools_type=$(echo "$tools_json" | jq -r 'type' 2>/dev/null || echo "invalid") # SUPPRESS:OK -- read-default: returns fallback if missing
|
|
298
|
+
if [[ "$tools_type" != "object" ]]; then
|
|
299
|
+
if [[ "$tools_json" =~ ^[0-9]+$ ]] && [[ ! "$tokens" =~ ^[0-9]+$ ]] && [[ "$chamber" =~ ^[0-9]+$ ]]; then
|
|
300
|
+
progress="$chamber"
|
|
301
|
+
chamber="$tokens"
|
|
302
|
+
tokens="$tools_json"
|
|
303
|
+
fi
|
|
304
|
+
tools_json="{}"
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# Ensure numeric fields are always valid for --argjson.
|
|
308
|
+
[[ "$tokens" =~ ^-?[0-9]+$ ]] || tokens=0
|
|
309
|
+
[[ "$progress" =~ ^-?[0-9]+$ ]] || progress=0
|
|
310
|
+
|
|
311
|
+
display_file="$COLONY_DATA_DIR/swarm-display.json"
|
|
312
|
+
|
|
313
|
+
# Initialize if doesn't exist
|
|
314
|
+
if [[ ! -f "$display_file" ]]; then
|
|
315
|
+
bash "$0" swarm-display-init "default-swarm" >/dev/null 2>&1 || _aether_log_error "Could not initialize swarm display"
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
319
|
+
|
|
320
|
+
# Read current display and update using jq
|
|
321
|
+
updated=$(jq --arg ant "$ant_name" --arg caste "$caste" --arg ant_status "$ant_status" \
|
|
322
|
+
--arg task "$task" --arg parent "$parent" --argjson tools "$tools_json" \
|
|
323
|
+
--argjson tokens "$tokens" --arg ts "$ts" --arg chamber "$chamber" --argjson progress "$progress" '
|
|
324
|
+
# Find existing ant or create new entry
|
|
325
|
+
(.active_ants | map(select(.name == $ant)) | length) as $exists |
|
|
326
|
+
# Get old chamber if ant exists
|
|
327
|
+
(if $exists > 0 then
|
|
328
|
+
(.active_ants[] | select(.name == $ant) | .chamber // "")
|
|
329
|
+
else
|
|
330
|
+
""
|
|
331
|
+
end) as $old_chamber |
|
|
332
|
+
# Determine new chamber
|
|
333
|
+
(if $chamber != "" then $chamber else $old_chamber end) as $new_chamber |
|
|
334
|
+
if $exists > 0 then
|
|
335
|
+
# Update existing ant
|
|
336
|
+
.active_ants = [.active_ants[] | if .name == $ant then
|
|
337
|
+
. + {
|
|
338
|
+
caste: $caste,
|
|
339
|
+
status: $ant_status,
|
|
340
|
+
task: $task,
|
|
341
|
+
parent: (if $parent != "" then $parent else .parent end),
|
|
342
|
+
tools: (if $tools != {} then $tools else .tools end),
|
|
343
|
+
tokens: (.tokens + $tokens),
|
|
344
|
+
chamber: (if $chamber != "" then $chamber else (.chamber // null) end),
|
|
345
|
+
progress: (if $progress > 0 then $progress else (.progress // 0) end),
|
|
346
|
+
updated_at: $ts
|
|
347
|
+
}
|
|
348
|
+
else . end]
|
|
349
|
+
else
|
|
350
|
+
# Add new ant
|
|
351
|
+
.active_ants += [{
|
|
352
|
+
name: $ant,
|
|
353
|
+
caste: $caste,
|
|
354
|
+
status: $ant_status,
|
|
355
|
+
task: $task,
|
|
356
|
+
parent: (if $parent != "" then $parent else null end),
|
|
357
|
+
tools: (if $tools != {} then $tools else {read:0,grep:0,edit:0,bash:0} end),
|
|
358
|
+
tokens: $tokens,
|
|
359
|
+
chamber: (if $chamber != "" then $chamber else null end),
|
|
360
|
+
progress: $progress,
|
|
361
|
+
started_at: $ts,
|
|
362
|
+
updated_at: $ts
|
|
363
|
+
}]
|
|
364
|
+
end |
|
|
365
|
+
# Recalculate summary
|
|
366
|
+
.summary.total_active = (.active_ants | length) |
|
|
367
|
+
.summary.by_caste = (.active_ants | group_by(.caste) | map({key: .[0].caste, value: length}) | from_entries) |
|
|
368
|
+
.summary.by_zone = (.active_ants | group_by(.status) | map({key: .[0].status, value: length}) | from_entries) |
|
|
369
|
+
# Update chamber activity counts
|
|
370
|
+
# Decrement old chamber if changed
|
|
371
|
+
(if $old_chamber != "" and $old_chamber != $new_chamber and has("chambers") and (.chambers | has($old_chamber)) then
|
|
372
|
+
.chambers[$old_chamber].activity = ([(.chambers[$old_chamber].activity // 1) - 1, 0] | max)
|
|
373
|
+
else
|
|
374
|
+
.
|
|
375
|
+
end) |
|
|
376
|
+
# Increment new chamber
|
|
377
|
+
(if $new_chamber != "" and has("chambers") and (.chambers | has($new_chamber)) then
|
|
378
|
+
.chambers[$new_chamber].activity = (.chambers[$new_chamber].activity // 0) + 1
|
|
379
|
+
else
|
|
380
|
+
.
|
|
381
|
+
end)
|
|
382
|
+
' "$display_file") || json_err "$E_JSON_INVALID" "Failed to update swarm display"
|
|
383
|
+
|
|
384
|
+
atomic_write "$display_file" "$updated"
|
|
385
|
+
|
|
386
|
+
# Get emoji for response
|
|
387
|
+
emoji=$(get_caste_emoji "$caste")
|
|
388
|
+
json_ok "$(jq -n --arg ant "$ant_name" --arg caste "$caste" --arg emoji "$emoji" \
|
|
389
|
+
--arg chamber "$chamber" --argjson progress "$progress" \
|
|
390
|
+
'{updated: true, ant: $ant, caste: $caste, emoji: $emoji, chamber: $chamber, progress: $progress}')"
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# ============================================================================
|
|
394
|
+
# _swarm_display_get
|
|
395
|
+
# Get current swarm display state
|
|
396
|
+
# Usage: _swarm_display_get
|
|
397
|
+
# ============================================================================
|
|
398
|
+
_swarm_display_get() {
|
|
399
|
+
display_file="$COLONY_DATA_DIR/swarm-display.json"
|
|
400
|
+
|
|
401
|
+
if [[ ! -f "$display_file" ]]; then
|
|
402
|
+
json_ok '{"swarm_id":null,"active_ants":[],"summary":{"total_active":0,"by_caste":{},"by_zone":{}},"chambers":{}}'
|
|
403
|
+
else
|
|
404
|
+
json_ok "$(cat "$display_file")"
|
|
405
|
+
fi
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
# ============================================================================
|
|
409
|
+
# _swarm_display_render
|
|
410
|
+
# Render the swarm display to terminal
|
|
411
|
+
# Usage: _swarm_display_render [swarm_id]
|
|
412
|
+
# ============================================================================
|
|
413
|
+
_swarm_display_render() {
|
|
414
|
+
_deprecation_warning "swarm-display-render"
|
|
415
|
+
swarm_id="${1:-default-swarm}"
|
|
416
|
+
|
|
417
|
+
display_script="$SCRIPT_DIR/utils/swarm-display.sh"
|
|
418
|
+
|
|
419
|
+
if [[ -f "$display_script" ]]; then
|
|
420
|
+
# Execute the display script
|
|
421
|
+
bash "$display_script" "$swarm_id" 2>/dev/null || _aether_log_error "Could not run swarm display script"
|
|
422
|
+
json_ok '{"rendered":true}'
|
|
423
|
+
else
|
|
424
|
+
json_err "$E_FILE_NOT_FOUND" "Display script not found: $display_script"
|
|
425
|
+
fi
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# ============================================================================
|
|
429
|
+
# Display helper functions (used by _swarm_display_inline)
|
|
430
|
+
# These are local helpers that were defined inside the swarm-display-inline case block
|
|
431
|
+
# ============================================================================
|
|
432
|
+
|
|
433
|
+
# Caste colors (ANSI)
|
|
434
|
+
_sw_get_caste_color() {
|
|
435
|
+
case "$1" in
|
|
436
|
+
builder) echo "$_SW_BLUE" ;;
|
|
437
|
+
watcher) echo "$_SW_GREEN" ;;
|
|
438
|
+
scout) echo "$_SW_YELLOW" ;;
|
|
439
|
+
chaos) echo "$_SW_RED" ;;
|
|
440
|
+
prime) echo "$_SW_MAGENTA" ;;
|
|
441
|
+
oracle) echo "$_SW_MAGENTA" ;;
|
|
442
|
+
route_setter) echo "$_SW_MAGENTA" ;;
|
|
443
|
+
*) echo "$_SW_RESET" ;;
|
|
444
|
+
esac
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
# Caste emojis with ant (local copy -- may differ from main file's get_caste_emoji)
|
|
448
|
+
_sw_get_caste_emoji() {
|
|
449
|
+
case "$1" in
|
|
450
|
+
builder) echo "🔨🐜" ;;
|
|
451
|
+
watcher) echo "👁️🐜" ;;
|
|
452
|
+
scout) echo "🔍🐜" ;;
|
|
453
|
+
chaos) echo "🎲🐜" ;;
|
|
454
|
+
prime) echo "👑🐜" ;;
|
|
455
|
+
oracle) echo "🔮🐜" ;;
|
|
456
|
+
route_setter) echo "🧭🐜" ;;
|
|
457
|
+
archaeologist) echo "🏺🐜" ;;
|
|
458
|
+
chronicler) echo "📝🐜" ;;
|
|
459
|
+
gatekeeper) echo "📦🐜" ;;
|
|
460
|
+
guardian) echo "🛡️🐜" ;;
|
|
461
|
+
includer) echo "♿🐜" ;;
|
|
462
|
+
keeper) echo "📚🐜" ;;
|
|
463
|
+
measurer) echo "⚡🐜" ;;
|
|
464
|
+
probe) echo "🧪🐜" ;;
|
|
465
|
+
sage) echo "📜🐜" ;;
|
|
466
|
+
tracker) echo "🐛🐜" ;;
|
|
467
|
+
weaver) echo "🔄🐜" ;;
|
|
468
|
+
colonizer) echo "🌱🐜" ;;
|
|
469
|
+
dreamer) echo "💭🐜" ;;
|
|
470
|
+
*) echo "🐜" ;;
|
|
471
|
+
esac
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
# Status phrases
|
|
475
|
+
_sw_get_status_phrase() {
|
|
476
|
+
case "$1" in
|
|
477
|
+
builder) echo "excavating..." ;;
|
|
478
|
+
watcher) echo "observing..." ;;
|
|
479
|
+
scout) echo "exploring..." ;;
|
|
480
|
+
chaos) echo "testing..." ;;
|
|
481
|
+
prime) echo "coordinating..." ;;
|
|
482
|
+
oracle) echo "researching..." ;;
|
|
483
|
+
route_setter) echo "planning..." ;;
|
|
484
|
+
*) echo "working..." ;;
|
|
485
|
+
esac
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
# Excavation phrase based on progress
|
|
489
|
+
_sw_get_excavation_phrase() {
|
|
490
|
+
local progress="${1:-0}"
|
|
491
|
+
if [[ "$progress" -lt 25 ]]; then
|
|
492
|
+
echo "🚧 Starting excavation..."
|
|
493
|
+
elif [[ "$progress" -lt 50 ]]; then
|
|
494
|
+
echo "⛏️ Digging deeper..."
|
|
495
|
+
elif [[ "$progress" -lt 75 ]]; then
|
|
496
|
+
echo "🪨 Moving earth..."
|
|
497
|
+
elif [[ "$progress" -lt 100 ]]; then
|
|
498
|
+
echo "🏗️ Almost there..."
|
|
499
|
+
else
|
|
500
|
+
echo "✅ Excavation complete!"
|
|
501
|
+
fi
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
# Format tools: "📖5 🔍3 ✏️2 ⚡1"
|
|
505
|
+
_sw_format_tools() {
|
|
506
|
+
local read="${1:-0}"
|
|
507
|
+
local grep="${2:-0}"
|
|
508
|
+
local edit="${3:-0}"
|
|
509
|
+
local bash="${4:-0}"
|
|
510
|
+
local result=""
|
|
511
|
+
[[ "$read" -gt 0 ]] && result="${result}📖${read} "
|
|
512
|
+
[[ "$grep" -gt 0 ]] && result="${result}🔍${grep} "
|
|
513
|
+
[[ "$edit" -gt 0 ]] && result="${result}✏️${edit} "
|
|
514
|
+
[[ "$bash" -gt 0 ]] && result="${result}⚡${bash}"
|
|
515
|
+
echo "$result"
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
# Render progress bar (green when working)
|
|
519
|
+
_sw_render_progress_bar() {
|
|
520
|
+
local percent="${1:-0}"
|
|
521
|
+
local width="${2:-20}"
|
|
522
|
+
[[ "$percent" -lt 0 ]] && percent=0
|
|
523
|
+
[[ "$percent" -gt 100 ]] && percent=100
|
|
524
|
+
local filled=$((percent * width / 100))
|
|
525
|
+
local empty=$((width - filled))
|
|
526
|
+
local bar=""
|
|
527
|
+
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
528
|
+
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
529
|
+
echo -e "${_SW_GREEN}[$bar]${_SW_RESET} ${percent}%"
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
# Format duration
|
|
533
|
+
_sw_format_duration() {
|
|
534
|
+
local seconds="${1:-0}"
|
|
535
|
+
if [[ "$seconds" -lt 60 ]]; then
|
|
536
|
+
echo "${seconds}s"
|
|
537
|
+
else
|
|
538
|
+
local mins=$((seconds / 60))
|
|
539
|
+
local secs=$((seconds % 60))
|
|
540
|
+
echo "${mins}m${secs}s"
|
|
541
|
+
fi
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
# ============================================================================
|
|
545
|
+
# _swarm_display_inline
|
|
546
|
+
# Inline swarm display for Claude Code (no loop, no clear)
|
|
547
|
+
# Usage: _swarm_display_inline [swarm_id]
|
|
548
|
+
# ============================================================================
|
|
549
|
+
_swarm_display_inline() {
|
|
550
|
+
_deprecation_warning "swarm-display-inline"
|
|
551
|
+
swarm_id="${1:-default-swarm}"
|
|
552
|
+
display_file="$COLONY_DATA_DIR/swarm-display.json"
|
|
553
|
+
|
|
554
|
+
# ANSI colors
|
|
555
|
+
_SW_BLUE='\033[34m'
|
|
556
|
+
_SW_GREEN='\033[32m'
|
|
557
|
+
_SW_YELLOW='\033[33m'
|
|
558
|
+
_SW_RED='\033[31m'
|
|
559
|
+
_SW_MAGENTA='\033[35m'
|
|
560
|
+
_SW_BOLD='\033[1m'
|
|
561
|
+
_SW_DIM='\033[2m'
|
|
562
|
+
_SW_RESET='\033[0m'
|
|
563
|
+
|
|
564
|
+
# Check for display file
|
|
565
|
+
if [[ ! -f "$display_file" ]]; then
|
|
566
|
+
echo -e "${_SW_DIM}🐜 No active swarm data${_SW_RESET}"
|
|
567
|
+
json_ok '{"displayed":false,"reason":"no_data"}'
|
|
568
|
+
exit 0
|
|
569
|
+
fi
|
|
570
|
+
|
|
571
|
+
# Check for jq
|
|
572
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
573
|
+
echo -e "${_SW_DIM}🐜 Swarm active (jq not available for details)${_SW_RESET}"
|
|
574
|
+
json_ok '{"displayed":true,"warning":"jq_missing"}'
|
|
575
|
+
exit 0
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
# Read swarm data
|
|
579
|
+
total_active=$(jq -r '.summary.total_active // 0' "$display_file" 2>/dev/null || echo "0") # SUPPRESS:OK -- read-default: file may not exist yet
|
|
580
|
+
|
|
581
|
+
if [[ "$total_active" -eq 0 ]]; then
|
|
582
|
+
echo -e "${_SW_DIM}🐜 Colony idle${_SW_RESET}"
|
|
583
|
+
json_ok '{"displayed":true,"ants":0}'
|
|
584
|
+
exit 0
|
|
585
|
+
fi
|
|
586
|
+
|
|
587
|
+
# Render header with ant logo
|
|
588
|
+
echo ""
|
|
589
|
+
cat << 'ANTLOGO'
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
▁▐▖ ▁
|
|
593
|
+
▗▇▇███▆▇▃▅████▆▇▆▅▟██▛▇
|
|
594
|
+
▝▜▅▛██████████████▜▅██
|
|
595
|
+
▁▂▀▇▆██▙▜██████████▛▟███▛▁▃▁
|
|
596
|
+
▕▂▁▉▅████▙▞██████▜█▚▟████▅▊ ▐
|
|
597
|
+
▗▁▐█▀▜████▛▃▝▁████▍▘▟▜████▛▀█▂ ▖
|
|
598
|
+
▁▎▝█▁▝▍▆▜████▊▐▀▏▀▍▂▂▝▀▕▀▌█████▀▅▐▚ █▏▁▁
|
|
599
|
+
▂▚▃▇▙█▟████▛▏ ▝▜▐▛▀▍▛▘ ▕█████▆▊▐▂▃▞▂▔
|
|
600
|
+
▚▔█▛██████▙▟▍▜▍▜▃▃▖▟▛▐██████▛▛▜▔▔▞
|
|
601
|
+
▋▖▍▊▖██████▇▃▁▝██▘▝▃████▜█▜ ▋▐▐▗
|
|
602
|
+
▍▌▇█▅▂▜██████████████████▉▃▄▋▖ ▝
|
|
603
|
+
▁▎▍▁▜▟███▀▀▜████████████▛▀▀███▆▂ ▁▁
|
|
604
|
+
██ ▆▇▌▁▕▚▅▆███▛████████▜███▆▄▞▁▁▐▅▎ █▉
|
|
605
|
+
▆█████▛▃▟█▀████████████████▛█▙▙▜▉▟▛▜█▌▗
|
|
606
|
+
▅▆▋ ▁▁▁▔▕▁▁▁▇█████▛▀▀▀▁▜▇▇▁▁▁▁▁▁▁▁ ▐▊▗
|
|
607
|
+
▗▆▃▃▃▔███▖▔██▀▀▝▀██▀▍█▛▁▐█▏█▛▀▀▏█▛▀▜█▆▃▃▆▖
|
|
608
|
+
▝▗▖ ▟█▟█▙ █▛▀▘ █▊ ▕█▛▀▜█▏█▛▀▘ █▋▆█▛ ▗▖
|
|
609
|
+
▘ ▘ ▟▛ ▝▀▘▀▀▀▀▘ ▀▀▂▂█▙▂▐▀▏▀▀▀▀▘▀▘ ▝▀▅▂▝ ▕▏
|
|
610
|
+
▕▕ ▃▗▄▔▗▄▄▗▗▗▔▄▄▄▄▗▄▄▗▔▃▃▃▗▄▂▄▃▗▄▂▖▖ ▏▁
|
|
611
|
+
▝▘▏ ▔▔ ▁▔▁▔▔▁▔▔▔▔▔▔▔▁▁ ▔▔ ▔▔▔▔
|
|
612
|
+
▀ ▀▝▘▀▀▔▘▘▀▝▕▀▀▝▝▀▔▀ ▀▔▘
|
|
613
|
+
▘ ▗▅▁▝▚▃▀▆▟██▙▆▝▃ ▘ ▁▗▌
|
|
614
|
+
▔▀▔▝ ▔▀▟▜▛▛▀▔ ▀
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
ANTLOGO
|
|
618
|
+
echo -e "${_SW_BOLD}AETHER COLONY :: Colony Activity${_SW_RESET}"
|
|
619
|
+
echo -e "${_SW_DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${_SW_RESET}"
|
|
620
|
+
echo ""
|
|
621
|
+
|
|
622
|
+
# Render each active ant (limit to 5)
|
|
623
|
+
# SUPPRESS:OK -- read-default: display file may not exist yet
|
|
624
|
+
jq -r '.active_ants[0:5][] | "\(.name)|\(.caste)|\(.status // "")|\(.task // "")|\(.tools.read // 0)|\(.tools.grep // 0)|\(.tools.edit // 0)|\(.tools.bash // 0)|\(.tokens // 0)|\(.started_at // "")|\(.parent // "Queen")|\(.progress // 0)"' "$display_file" 2>/dev/null | while IFS='|' read -r ant_name ant_caste ant_status ant_task read_ct grep_ct edit_ct bash_ct tokens started_at parent progress; do
|
|
625
|
+
color=$(_sw_get_caste_color "$ant_caste")
|
|
626
|
+
emoji=$(_sw_get_caste_emoji "$ant_caste")
|
|
627
|
+
phrase=$(_sw_get_status_phrase "$ant_caste")
|
|
628
|
+
|
|
629
|
+
# Format tools
|
|
630
|
+
tools_str=$(_sw_format_tools "$read_ct" "$grep_ct" "$edit_ct" "$bash_ct")
|
|
631
|
+
|
|
632
|
+
# Truncate task if too long
|
|
633
|
+
display_task="$ant_task"
|
|
634
|
+
[[ ${#display_task} -gt 35 ]] && display_task="${display_task:0:32}..."
|
|
635
|
+
|
|
636
|
+
# Calculate elapsed time
|
|
637
|
+
elapsed_str=""
|
|
638
|
+
started_ts="${started_at:-}"
|
|
639
|
+
if [[ -n "$started_ts" ]] && [[ "$started_ts" != "null" ]]; then
|
|
640
|
+
started_ts=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$started_ts" +%s 2>/dev/null) # SUPPRESS:OK -- cross-platform: macOS date syntax
|
|
641
|
+
if [[ -z "$started_ts" ]] || [[ "$started_ts" == "null" ]]; then
|
|
642
|
+
started_ts=$(date -d "$started_ts" +%s 2>/dev/null) || started_ts=0 # SUPPRESS:OK -- cross-platform: Linux date syntax
|
|
643
|
+
fi
|
|
644
|
+
now_ts=$(date +%s)
|
|
645
|
+
elapsed=0
|
|
646
|
+
if [[ -n "$started_ts" ]] && [[ "$started_ts" -gt 0 ]] 2>/dev/null; then # SUPPRESS:OK -- existence-test: value may not be numeric
|
|
647
|
+
elapsed=$((now_ts - started_ts))
|
|
648
|
+
fi
|
|
649
|
+
if [[ ${elapsed:-0} -gt 0 ]]; then
|
|
650
|
+
elapsed_str="($(_sw_format_duration $elapsed))"
|
|
651
|
+
fi
|
|
652
|
+
fi
|
|
653
|
+
|
|
654
|
+
# Token indicator
|
|
655
|
+
token_str=""
|
|
656
|
+
if [[ -n "$tokens" ]] && [[ "$tokens" -gt 0 ]]; then
|
|
657
|
+
token_str="🍯${tokens}"
|
|
658
|
+
fi
|
|
659
|
+
|
|
660
|
+
# Output ant line: "🐜 Builder: excavating... Implement auth 📖5 🔍3 (2m3s) 🍯1250"
|
|
661
|
+
echo -e "${color}${emoji} ${_SW_BOLD}${ant_name}${_SW_RESET}${color}: ${phrase}${_SW_RESET} ${display_task}"
|
|
662
|
+
echo -e " ${tools_str} ${_SW_DIM}${elapsed_str}${_SW_RESET} ${token_str}"
|
|
663
|
+
|
|
664
|
+
# Show progress bar if progress > 0
|
|
665
|
+
if [[ -n "$progress" ]] && [[ "$progress" -gt 0 ]]; then
|
|
666
|
+
progress_bar=$(_sw_render_progress_bar "$progress" 15)
|
|
667
|
+
excavation_phrase=$(_sw_get_excavation_phrase "$progress")
|
|
668
|
+
echo -e " ${_SW_DIM}${progress_bar}${_SW_RESET}"
|
|
669
|
+
echo -e " ${_SW_DIM}${excavation_phrase}${_SW_RESET}"
|
|
670
|
+
fi
|
|
671
|
+
|
|
672
|
+
echo ""
|
|
673
|
+
done
|
|
674
|
+
|
|
675
|
+
# Chamber activity map
|
|
676
|
+
echo -e "${_SW_DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${_SW_RESET}"
|
|
677
|
+
echo ""
|
|
678
|
+
echo -e "${_SW_BOLD}Chamber Activity:${_SW_RESET}"
|
|
679
|
+
|
|
680
|
+
# Show active chambers with fire intensity
|
|
681
|
+
has_chamber_activity=0
|
|
682
|
+
# SUPPRESS:OK -- read-default: returns fallback on failure
|
|
683
|
+
jq -r '.chambers | to_entries[] | "\(.key)|\(.value.activity)|\(.value.icon)"' "$display_file" 2>/dev/null | \
|
|
684
|
+
while IFS='|' read -r chamber activity icon; do
|
|
685
|
+
if [[ -n "$activity" ]] && [[ "$activity" -gt 0 ]]; then
|
|
686
|
+
has_chamber_activity=1
|
|
687
|
+
if [[ "$activity" -ge 5 ]]; then
|
|
688
|
+
fires="🔥🔥🔥"
|
|
689
|
+
elif [[ "$activity" -ge 3 ]]; then
|
|
690
|
+
fires="🔥🔥"
|
|
691
|
+
else
|
|
692
|
+
fires="🔥"
|
|
693
|
+
fi
|
|
694
|
+
chamber_name="${chamber//_/ }"
|
|
695
|
+
echo -e " ${icon} ${chamber_name} ${fires} (${activity} ants)"
|
|
696
|
+
fi
|
|
697
|
+
done
|
|
698
|
+
|
|
699
|
+
if [[ "$has_chamber_activity" -eq 0 ]]; then
|
|
700
|
+
echo -e "${_SW_DIM} (no chamber activity)${_SW_RESET}"
|
|
701
|
+
fi
|
|
702
|
+
|
|
703
|
+
# Summary
|
|
704
|
+
echo ""
|
|
705
|
+
echo -e "${_SW_DIM}${total_active} forager$([[ "$total_active" -eq 1 ]] || echo "s") excavating...${_SW_RESET}"
|
|
706
|
+
|
|
707
|
+
json_ok "{\"displayed\":true,\"ants\":$total_active}"
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
# ============================================================================
|
|
711
|
+
# Display helper functions (used by _swarm_display_text)
|
|
712
|
+
# These are local helpers that were defined inside the swarm-display-text case block
|
|
713
|
+
# ============================================================================
|
|
714
|
+
|
|
715
|
+
# Caste emoji lookup (text-only version)
|
|
716
|
+
_sw_get_emoji() {
|
|
717
|
+
case "$1" in
|
|
718
|
+
builder) echo "🔨🐜" ;;
|
|
719
|
+
watcher) echo "👁️🐜" ;;
|
|
720
|
+
scout) echo "🔍🐜" ;;
|
|
721
|
+
chaos) echo "🎲🐜" ;;
|
|
722
|
+
prime) echo "👑🐜" ;;
|
|
723
|
+
oracle) echo "🔮🐜" ;;
|
|
724
|
+
route_setter) echo "🧭🐜" ;;
|
|
725
|
+
archaeologist) echo "🏺🐜" ;;
|
|
726
|
+
surveyor) echo "📊🐜" ;;
|
|
727
|
+
*) echo "🐜" ;;
|
|
728
|
+
esac
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
# Format tool counts (only non-zero)
|
|
732
|
+
_sw_format_tools_text() {
|
|
733
|
+
local r="${1:-0}" g="${2:-0}" e="${3:-0}" b="${4:-0}"
|
|
734
|
+
local result=""
|
|
735
|
+
[[ "$r" -gt 0 ]] && result="${result}📖${r} "
|
|
736
|
+
[[ "$g" -gt 0 ]] && result="${result}🔍${g} "
|
|
737
|
+
[[ "$e" -gt 0 ]] && result="${result}✏️${e} "
|
|
738
|
+
[[ "$b" -gt 0 ]] && result="${result}⚡${b}"
|
|
739
|
+
echo "$result"
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
# Progress bar using block characters (no ANSI)
|
|
743
|
+
_sw_render_bar_text() {
|
|
744
|
+
local pct="${1:-0}" w="${2:-10}"
|
|
745
|
+
[[ "$pct" -lt 0 ]] && pct=0
|
|
746
|
+
[[ "$pct" -gt 100 ]] && pct=100
|
|
747
|
+
local filled=$((pct * w / 100))
|
|
748
|
+
local empty=$((w - filled))
|
|
749
|
+
local bar=""
|
|
750
|
+
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
751
|
+
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
752
|
+
echo "[$bar] ${pct}%"
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
# Helper: parse ISO-8601 timestamp to epoch (macOS + Linux)
|
|
756
|
+
_sw_iso_to_epoch_text() {
|
|
757
|
+
local iso="$1"
|
|
758
|
+
local epoch=""
|
|
759
|
+
epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" +%s 2>/dev/null || true) # SUPPRESS:OK -- cross-platform: macOS date syntax
|
|
760
|
+
if [[ -z "$epoch" ]]; then
|
|
761
|
+
epoch=$(date -d "$iso" +%s 2>/dev/null || true) # SUPPRESS:OK -- cross-platform: Linux date syntax
|
|
762
|
+
fi
|
|
763
|
+
echo "${epoch:-0}"
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
# Helper: duration formatter (e.g., 45s, 3m12s)
|
|
767
|
+
_sw_format_duration_text() {
|
|
768
|
+
local seconds="${1:-0}"
|
|
769
|
+
if [[ "$seconds" -lt 60 ]]; then
|
|
770
|
+
echo "${seconds}s"
|
|
771
|
+
else
|
|
772
|
+
local mins=$((seconds / 60))
|
|
773
|
+
local secs=$((seconds % 60))
|
|
774
|
+
echo "${mins}m${secs}s"
|
|
775
|
+
fi
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
# Helper: compact number formatter (e.g., 1.2k, 2.4M)
|
|
779
|
+
_sw_format_compact_tokens() {
|
|
780
|
+
local n="${1:-0}"
|
|
781
|
+
if [[ "$n" -ge 1000000 ]]; then
|
|
782
|
+
awk -v n="$n" 'BEGIN { printf "%.1fM", n/1000000 }'
|
|
783
|
+
elif [[ "$n" -ge 1000 ]]; then
|
|
784
|
+
awk -v n="$n" 'BEGIN { printf "%.1fk", n/1000 }'
|
|
785
|
+
else
|
|
786
|
+
echo "$n"
|
|
787
|
+
fi
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
# ============================================================================
|
|
791
|
+
# _swarm_display_text
|
|
792
|
+
# Plain-text swarm display for Claude conversation (no ANSI codes)
|
|
793
|
+
# Usage: _swarm_display_text [swarm_id]
|
|
794
|
+
# ============================================================================
|
|
795
|
+
_swarm_display_text() {
|
|
796
|
+
swarm_id="${1:-default-swarm}"
|
|
797
|
+
display_file="$COLONY_DATA_DIR/swarm-display.json"
|
|
798
|
+
|
|
799
|
+
# Check for display file
|
|
800
|
+
if [[ ! -f "$display_file" ]]; then
|
|
801
|
+
echo "🐜 Colony idle"
|
|
802
|
+
json_ok '{"displayed":false,"reason":"no_data"}'
|
|
803
|
+
exit 0
|
|
804
|
+
fi
|
|
805
|
+
|
|
806
|
+
# Check for jq
|
|
807
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
808
|
+
echo "🐜 Swarm active (details unavailable)"
|
|
809
|
+
json_ok '{"displayed":true,"warning":"jq_missing"}'
|
|
810
|
+
exit 0
|
|
811
|
+
fi
|
|
812
|
+
|
|
813
|
+
# Read swarm data — handle both flat total_active and nested .summary.total_active
|
|
814
|
+
# SUPPRESS:OK -- read-default: query may return empty
|
|
815
|
+
total_active=$(jq -r '(.total_active // .summary.total_active // 0)' "$display_file" 2>/dev/null || echo "0")
|
|
816
|
+
|
|
817
|
+
if [[ "$total_active" -eq 0 ]]; then
|
|
818
|
+
echo "🐜 Colony idle"
|
|
819
|
+
json_ok '{"displayed":true,"ants":0}'
|
|
820
|
+
exit 0
|
|
821
|
+
fi
|
|
822
|
+
|
|
823
|
+
# Compact header
|
|
824
|
+
echo "🐜 COLONY ACTIVITY"
|
|
825
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
826
|
+
|
|
827
|
+
# SUPPRESS:OK -- read-default: query may return empty
|
|
828
|
+
total_tokens=$(jq -r '[.active_ants[]?.tokens // 0] | add // 0' "$display_file" 2>/dev/null || echo "0")
|
|
829
|
+
started_iso=$(jq -r '.timestamp // ""' "$display_file" 2>/dev/null || echo "") # SUPPRESS:OK -- read-default: file may not exist yet
|
|
830
|
+
elapsed_text="n/a"
|
|
831
|
+
if [[ -n "$started_iso" && "$started_iso" != "null" ]]; then
|
|
832
|
+
started_epoch=$(_sw_iso_to_epoch_text "$started_iso")
|
|
833
|
+
now_epoch=$(date +%s)
|
|
834
|
+
if [[ "$started_epoch" -gt 0 ]] 2>/dev/null; then # SUPPRESS:OK -- existence-test: value may not be numeric
|
|
835
|
+
total_elapsed=$((now_epoch - started_epoch))
|
|
836
|
+
[[ "$total_elapsed" -lt 0 ]] && total_elapsed=0
|
|
837
|
+
elapsed_text=$(_sw_format_duration_text "$total_elapsed")
|
|
838
|
+
fi
|
|
839
|
+
fi
|
|
840
|
+
|
|
841
|
+
# Render each ant (max 5)
|
|
842
|
+
# SUPPRESS:OK -- read-default: query may return empty
|
|
843
|
+
jq -r '.active_ants[0:5][] | "\(.name)|\(.caste)|\(.task // "")|\(.tools.read // 0)|\(.tools.grep // 0)|\(.tools.edit // 0)|\(.tools.bash // 0)|\(.progress // 0)|\(.tokens // 0)|\(.started_at // "")"' "$display_file" 2>/dev/null | while IFS='|' read -r name caste task r g e b progress tokens started_at; do
|
|
844
|
+
emoji=$(_sw_get_emoji "$caste")
|
|
845
|
+
tools=$(_sw_format_tools_text "$r" "$g" "$e" "$b")
|
|
846
|
+
bar=$(_sw_render_bar_text "${progress:-0}" 10)
|
|
847
|
+
token_str=""
|
|
848
|
+
elapsed_ant=""
|
|
849
|
+
|
|
850
|
+
# Truncate task to 25 chars
|
|
851
|
+
[[ ${#task} -gt 25 ]] && task="${task:0:22}..."
|
|
852
|
+
|
|
853
|
+
if [[ -n "$tokens" && "$tokens" -gt 0 ]] 2>/dev/null; then # SUPPRESS:OK -- existence-test: value may not be numeric
|
|
854
|
+
token_str="🍯$(_sw_format_compact_tokens "$tokens")"
|
|
855
|
+
fi
|
|
856
|
+
|
|
857
|
+
if [[ -n "$started_at" && "$started_at" != "null" ]]; then
|
|
858
|
+
ant_start_epoch=$(_sw_iso_to_epoch_text "$started_at")
|
|
859
|
+
now_epoch=$(date +%s)
|
|
860
|
+
if [[ "$ant_start_epoch" -gt 0 ]] 2>/dev/null; then # SUPPRESS:OK -- existence-test: value may not be numeric
|
|
861
|
+
ant_elapsed=$((now_epoch - ant_start_epoch))
|
|
862
|
+
[[ "$ant_elapsed" -lt 0 ]] && ant_elapsed=0
|
|
863
|
+
elapsed_ant="($(_sw_format_duration_text "$ant_elapsed"))"
|
|
864
|
+
fi
|
|
865
|
+
fi
|
|
866
|
+
|
|
867
|
+
echo "${emoji} ${name} ${bar} ${task}"
|
|
868
|
+
meta_line=""
|
|
869
|
+
[[ -n "$tools" ]] && meta_line="${meta_line}${tools} "
|
|
870
|
+
[[ -n "$elapsed_ant" ]] && meta_line="${meta_line}${elapsed_ant} "
|
|
871
|
+
[[ -n "$token_str" ]] && meta_line="${meta_line}${token_str}"
|
|
872
|
+
[[ -n "$meta_line" ]] && echo " ${meta_line}"
|
|
873
|
+
echo ""
|
|
874
|
+
done
|
|
875
|
+
|
|
876
|
+
# Overflow indicator
|
|
877
|
+
if [[ "$total_active" -gt 5 ]]; then
|
|
878
|
+
echo " +$((total_active - 5)) more ants..."
|
|
879
|
+
echo ""
|
|
880
|
+
fi
|
|
881
|
+
|
|
882
|
+
# Footer
|
|
883
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
884
|
+
echo "⏱️ Elapsed: ${elapsed_text} | 🍯 Total: $(_sw_format_compact_tokens "$total_tokens") | ${total_active} ants active"
|
|
885
|
+
|
|
886
|
+
json_ok "{\"displayed\":true,\"ants\":$total_active}"
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
# ============================================================================
|
|
890
|
+
# _swarm_timing_start
|
|
891
|
+
# Record start time for an ant
|
|
892
|
+
# Usage: _swarm_timing_start <ant_name>
|
|
893
|
+
# ============================================================================
|
|
894
|
+
_swarm_timing_start() {
|
|
895
|
+
ant_name="${1:-}"
|
|
896
|
+
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-start <ant_name>"
|
|
897
|
+
|
|
898
|
+
mkdir -p "$COLONY_DATA_DIR"
|
|
899
|
+
timing_file="$COLONY_DATA_DIR/timing.log"
|
|
900
|
+
ts=$(date +%s)
|
|
901
|
+
ts_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
902
|
+
|
|
903
|
+
# Remove any existing entry for this ant and append new one
|
|
904
|
+
if [[ -f "$timing_file" ]]; then
|
|
905
|
+
# -F: ant_name may contain regex metacharacters; ^ anchor dropped (ant names are unique per swarm, no substring collision)
|
|
906
|
+
grep -vF "$ant_name|" "$timing_file" > "${timing_file}.tmp" 2>/dev/null || true # SUPPRESS:OK -- read-default: file may not exist
|
|
907
|
+
mv "${timing_file}.tmp" "$timing_file"
|
|
908
|
+
fi
|
|
909
|
+
echo "$ant_name|$ts|$ts_iso" >> "$timing_file"
|
|
910
|
+
|
|
911
|
+
json_ok "$(jq -n --arg ant "$ant_name" --arg started_at "$ts_iso" --argjson timestamp "$ts" \
|
|
912
|
+
'{ant: $ant, started_at: $started_at, timestamp: $timestamp}')"
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
# ============================================================================
|
|
916
|
+
# _swarm_timing_get
|
|
917
|
+
# Get elapsed time for an ant
|
|
918
|
+
# Usage: _swarm_timing_get <ant_name>
|
|
919
|
+
# ============================================================================
|
|
920
|
+
_swarm_timing_get() {
|
|
921
|
+
ant_name="${1:-}"
|
|
922
|
+
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-get <ant_name>"
|
|
923
|
+
|
|
924
|
+
timing_file="$COLONY_DATA_DIR/timing.log"
|
|
925
|
+
|
|
926
|
+
# -F: ant_name may contain regex metacharacters; ^ anchor dropped (ant names are unique per swarm)
|
|
927
|
+
if [[ ! -f "$timing_file" ]] || ! grep -qF "$ant_name|" "$timing_file" 2>/dev/null; then # SUPPRESS:OK -- existence-test: file may not exist
|
|
928
|
+
json_ok "$(jq -n --arg ant "$ant_name" '{ant: $ant, started_at: null, elapsed_seconds: 0, elapsed_formatted: "00:00"}')"
|
|
929
|
+
exit 0
|
|
930
|
+
fi
|
|
931
|
+
|
|
932
|
+
# Read start time
|
|
933
|
+
start_line=$(grep -F "$ant_name|" "$timing_file" | tail -1)
|
|
934
|
+
start_ts=$(echo "$start_line" | cut -d'|' -f2)
|
|
935
|
+
start_iso=$(echo "$start_line" | cut -d'|' -f3)
|
|
936
|
+
|
|
937
|
+
now=$(date +%s)
|
|
938
|
+
elapsed=$((now - start_ts))
|
|
939
|
+
|
|
940
|
+
# Format as MM:SS
|
|
941
|
+
mins=$((elapsed / 60))
|
|
942
|
+
secs=$((elapsed % 60))
|
|
943
|
+
formatted=$(printf "%02d:%02d" $mins $secs)
|
|
944
|
+
|
|
945
|
+
json_ok "$(jq -n --arg ant "$ant_name" --arg started_at "$start_iso" \
|
|
946
|
+
--argjson elapsed "$elapsed" --arg formatted "$formatted" \
|
|
947
|
+
'{ant: $ant, started_at: $started_at, elapsed_seconds: $elapsed, elapsed_formatted: $formatted}')"
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
# ============================================================================
|
|
951
|
+
# _swarm_timing_eta
|
|
952
|
+
# Calculate ETA based on progress percentage
|
|
953
|
+
# Usage: _swarm_timing_eta <ant_name> <percent_complete>
|
|
954
|
+
# ============================================================================
|
|
955
|
+
_swarm_timing_eta() {
|
|
956
|
+
ant_name="${1:-}"
|
|
957
|
+
percent="${2:-0}"
|
|
958
|
+
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-eta <ant_name> <percent_complete>"
|
|
959
|
+
|
|
960
|
+
# Validate percent is a number
|
|
961
|
+
if ! [[ "$percent" =~ ^[0-9]+$ ]]; then
|
|
962
|
+
percent=0
|
|
963
|
+
fi
|
|
964
|
+
|
|
965
|
+
# Clamp percent to 0-100
|
|
966
|
+
if [[ $percent -lt 0 ]]; then
|
|
967
|
+
percent=0
|
|
968
|
+
elif [[ $percent -gt 100 ]]; then
|
|
969
|
+
percent=100
|
|
970
|
+
fi
|
|
971
|
+
|
|
972
|
+
timing_file="$COLONY_DATA_DIR/timing.log"
|
|
973
|
+
|
|
974
|
+
# -F: ant_name may contain regex metacharacters; ^ anchor dropped (ant names are unique per swarm)
|
|
975
|
+
if [[ ! -f "$timing_file" ]] || ! grep -qF "$ant_name|" "$timing_file" 2>/dev/null; then # SUPPRESS:OK -- existence-test: file may not exist
|
|
976
|
+
json_ok "$(jq -n --arg ant "$ant_name" --argjson percent "$percent" \
|
|
977
|
+
'{ant: $ant, percent: $percent, eta_seconds: null, eta_formatted: "--:--"}')"
|
|
978
|
+
exit 0
|
|
979
|
+
fi
|
|
980
|
+
|
|
981
|
+
# Read start time
|
|
982
|
+
start_ts=$(grep -F "$ant_name|" "$timing_file" | tail -1 | cut -d'|' -f2)
|
|
983
|
+
now=$(date +%s)
|
|
984
|
+
elapsed=$((now - start_ts))
|
|
985
|
+
|
|
986
|
+
# Calculate ETA
|
|
987
|
+
if [[ $percent -le 0 ]]; then
|
|
988
|
+
eta_seconds=null
|
|
989
|
+
eta_formatted="--:--"
|
|
990
|
+
elif [[ $percent -ge 100 ]]; then
|
|
991
|
+
eta_seconds=0
|
|
992
|
+
eta_formatted="00:00"
|
|
993
|
+
else
|
|
994
|
+
# ETA = (elapsed / percent) * (100 - percent)
|
|
995
|
+
eta_seconds=$(( (elapsed * (100 - percent)) / percent ))
|
|
996
|
+
mins=$((eta_seconds / 60))
|
|
997
|
+
secs=$((eta_seconds % 60))
|
|
998
|
+
eta_formatted=$(printf "%02d:%02d" $mins $secs)
|
|
999
|
+
fi
|
|
1000
|
+
|
|
1001
|
+
json_ok "$(jq -n --arg ant "$ant_name" --argjson percent "$percent" \
|
|
1002
|
+
--argjson eta "$eta_seconds" --arg eta_fmt "$eta_formatted" \
|
|
1003
|
+
'{ant: $ant, percent: $percent, eta_seconds: $eta, eta_formatted: $eta_fmt}')"
|
|
1004
|
+
}
|