aether-colony 3.1.17 → 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 +5354 -0
- 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 +211 -0
- package/.aether/docs/README.md +98 -0
- package/.aether/docs/caste-system.md +48 -0
- 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/{runtime → .aether/docs/disciplines}/DISCIPLINES.md +13 -11
- package/.aether/docs/error-codes.md +268 -0
- package/.aether/docs/known-issues.md +94 -0
- package/{runtime → .aether}/docs/pheromones.md +86 -6
- package/.aether/docs/plans/pheromone-display-plan.md +257 -0
- package/.aether/docs/queen-commands.md +98 -0
- package/.aether/docs/source-of-truth-map.md +132 -0
- package/.aether/docs/xml-utilities.md +47 -0
- package/{runtime → .aether}/exchange/pheromone-xml.sh +2 -1
- package/{runtime → .aether}/exchange/registry-xml.sh +7 -3
- package/{runtime → .aether}/exchange/wisdom-xml.sh +11 -4
- package/.aether/rules/aether-colony.md +144 -0
- package/.aether/schemas/example-prompt-builder.xml +234 -0
- 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 +61 -0
- package/.aether/templates/colony-state-reset.jq.template +23 -0
- package/.aether/templates/colony-state.template.json +39 -0
- package/.aether/templates/constraints.template.json +9 -0
- package/.aether/templates/crowned-anthill.template.md +36 -0
- package/.aether/templates/handoff-build-error.template.md +30 -0
- package/.aether/templates/handoff-build-success.template.md +39 -0
- package/.aether/templates/handoff.template.md +40 -0
- package/.aether/templates/learning-observations.template.json +6 -0
- package/.aether/templates/midden.template.json +13 -0
- package/.aether/templates/pheromones.template.json +6 -0
- package/.aether/templates/session.template.json +9 -0
- package/{runtime → .aether}/utils/atomic-write.sh +68 -22
- package/{runtime → .aether}/utils/chamber-compare.sh +23 -10
- package/.aether/utils/chamber-utils.sh +440 -0
- package/.aether/utils/emoji-audit.sh +166 -0
- package/{runtime → .aether}/utils/error-handler.sh +34 -8
- package/.aether/utils/file-lock.sh +313 -0
- 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 +415 -0
- package/.aether/utils/session.sh +552 -0
- package/.aether/utils/skills.sh +509 -0
- package/.aether/utils/spawn-tree.sh +260 -0
- package/.aether/utils/spawn.sh +260 -0
- package/.aether/utils/state-api.sh +199 -0
- package/{runtime → .aether}/utils/state-loader.sh +8 -6
- package/.aether/utils/suggest.sh +611 -0
- package/{runtime → .aether}/utils/swarm-display.sh +10 -1
- package/.aether/utils/swarm.sh +1004 -0
- package/{runtime → .aether}/utils/watch-spawn-tree.sh +11 -2
- package/{runtime → .aether}/utils/xml-compose.sh +9 -3
- package/.aether/utils/xml-convert.sh +277 -0
- package/{runtime → .aether}/utils/xml-core.sh +5 -9
- package/.aether/utils/xml-query.sh +201 -0
- package/.aether/utils/xml-utils.sh +110 -0
- package/{runtime → .aether}/workers.md +97 -81
- package/.claude/agents/ant/aether-ambassador.md +265 -0
- package/.claude/agents/ant/aether-archaeologist.md +327 -0
- package/.claude/agents/ant/aether-architect.md +236 -0
- package/.claude/agents/ant/aether-auditor.md +271 -0
- package/.claude/agents/ant/aether-builder.md +224 -0
- package/.claude/agents/ant/aether-chaos.md +269 -0
- package/.claude/agents/ant/aether-chronicler.md +305 -0
- package/.claude/agents/ant/aether-gatekeeper.md +330 -0
- package/.claude/agents/ant/aether-includer.md +374 -0
- package/.claude/agents/ant/aether-keeper.md +272 -0
- package/.claude/agents/ant/aether-measurer.md +322 -0
- package/.claude/agents/ant/aether-oracle.md +237 -0
- package/.claude/agents/ant/aether-probe.md +211 -0
- package/.claude/agents/ant/aether-queen.md +330 -0
- package/.claude/agents/ant/aether-route-setter.md +178 -0
- package/.claude/agents/ant/aether-sage.md +418 -0
- package/.claude/agents/ant/aether-scout.md +179 -0
- package/.claude/agents/ant/aether-surveyor-disciplines.md +417 -0
- package/.claude/agents/ant/aether-surveyor-nest.md +355 -0
- package/.claude/agents/ant/aether-surveyor-pathogens.md +289 -0
- package/.claude/agents/ant/aether-surveyor-provisions.md +360 -0
- package/.claude/agents/ant/aether-tracker.md +270 -0
- package/.claude/agents/ant/aether-watcher.md +280 -0
- package/.claude/agents/ant/aether-weaver.md +248 -0
- package/.claude/commands/ant/archaeology.md +16 -14
- package/.claude/commands/ant/build.md +43 -1028
- package/.claude/commands/ant/chaos.md +19 -23
- package/.claude/commands/ant/colonize.md +52 -31
- package/.claude/commands/ant/continue.md +40 -1016
- package/.claude/commands/ant/council.md +21 -18
- package/.claude/commands/ant/data-clean.md +81 -0
- package/.claude/commands/ant/dream.md +27 -15
- package/.claude/commands/ant/entomb.md +317 -225
- package/.claude/commands/ant/export-signals.md +57 -0
- package/.claude/commands/ant/feedback.md +48 -26
- package/.claude/commands/ant/flag.md +30 -10
- package/.claude/commands/ant/flags.md +34 -12
- package/.claude/commands/ant/focus.md +45 -19
- package/.claude/commands/ant/help.md +50 -8
- package/.claude/commands/ant/history.md +13 -0
- package/.claude/commands/ant/import-signals.md +71 -0
- package/.claude/commands/ant/init.md +345 -194
- package/.claude/commands/ant/insert-phase.md +101 -0
- package/.claude/commands/ant/interpret.md +26 -4
- package/.claude/commands/ant/lay-eggs.md +184 -127
- package/.claude/commands/ant/maturity.md +32 -11
- package/.claude/commands/ant/memory-details.md +77 -0
- package/.claude/commands/ant/migrate-state.md +20 -2
- package/.claude/commands/ant/oracle.md +337 -74
- package/.claude/commands/ant/organize.md +39 -25
- package/.claude/commands/ant/patrol.md +620 -0
- package/.claude/commands/ant/pause-colony.md +23 -27
- package/.claude/commands/ant/phase.md +40 -42
- package/.claude/commands/ant/pheromones.md +156 -0
- package/.claude/commands/ant/plan.md +185 -51
- package/.claude/commands/ant/preferences.md +65 -0
- package/.claude/commands/ant/redirect.md +45 -30
- package/.claude/commands/ant/resume-colony.md +51 -27
- package/.claude/commands/ant/resume.md +314 -94
- package/.claude/commands/ant/run.md +195 -0
- package/.claude/commands/ant/seal.md +650 -221
- package/.claude/commands/ant/skill-create.md +286 -0
- package/.claude/commands/ant/status.md +196 -31
- package/.claude/commands/ant/swarm.md +16 -46
- package/.claude/commands/ant/tunnels.md +280 -105
- package/.claude/commands/ant/update.md +73 -89
- package/.claude/commands/ant/verify-castes.md +100 -42
- package/.claude/commands/ant/watch.md +14 -12
- package/.opencode/agents/aether-ambassador.md +63 -20
- package/.opencode/agents/aether-archaeologist.md +29 -12
- package/.opencode/agents/aether-architect.md +103 -36
- package/.opencode/agents/aether-auditor.md +51 -18
- package/.opencode/agents/aether-builder.md +70 -20
- package/.opencode/agents/aether-chaos.md +29 -12
- package/.opencode/agents/aether-chronicler.md +60 -18
- package/.opencode/agents/aether-gatekeeper.md +27 -18
- package/.opencode/agents/aether-includer.md +27 -18
- package/.opencode/agents/aether-keeper.md +89 -18
- package/.opencode/agents/aether-measurer.md +27 -18
- package/.opencode/agents/aether-oracle.md +137 -0
- package/.opencode/agents/aether-probe.md +60 -18
- package/.opencode/agents/aether-queen.md +172 -24
- package/.opencode/agents/aether-route-setter.md +57 -12
- package/.opencode/agents/aether-sage.md +26 -18
- package/.opencode/agents/aether-scout.md +28 -20
- package/.opencode/agents/aether-surveyor-disciplines.md +59 -2
- package/.opencode/agents/aether-surveyor-nest.md +59 -2
- package/.opencode/agents/aether-surveyor-pathogens.md +57 -2
- package/.opencode/agents/aether-surveyor-provisions.md +59 -2
- package/.opencode/agents/aether-tracker.md +64 -18
- package/.opencode/agents/aether-watcher.md +66 -19
- package/.opencode/agents/aether-weaver.md +61 -18
- package/.opencode/commands/ant/archaeology.md +7 -14
- package/.opencode/commands/ant/build.md +437 -257
- package/.opencode/commands/ant/chaos.md +7 -24
- package/.opencode/commands/ant/colonize.md +8 -17
- package/.opencode/commands/ant/continue.md +661 -142
- 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 +133 -62
- 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 +374 -167
- 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 +18 -16
- 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 +385 -0
- package/.opencode/commands/ant/run.md +201 -0
- package/.opencode/commands/ant/seal.md +259 -45
- package/.opencode/commands/ant/skill-create.md +63 -0
- package/.opencode/commands/ant/status.md +135 -31
- package/.opencode/commands/ant/swarm.md +3 -345
- package/.opencode/commands/ant/tunnels.md +152 -9
- package/.opencode/commands/ant/update.md +70 -91
- package/.opencode/commands/ant/verify-castes.md +96 -42
- package/.opencode/commands/ant/watch.md +7 -0
- package/CHANGELOG.md +356 -0
- package/README.md +203 -573
- package/bin/cli.js +455 -527
- package/bin/generate-commands.js +186 -0
- package/bin/generate-commands.sh +127 -88
- package/bin/lib/init.js +13 -3
- package/bin/lib/spawn-logger.js +0 -15
- package/bin/lib/update-transaction.js +392 -140
- package/bin/npx-install.js +178 -0
- package/bin/sync-to-runtime.sh +5 -137
- package/bin/validate-package.sh +166 -0
- package/package.json +14 -7
- package/.opencode/agents/aether-guardian.md +0 -107
- package/.opencode/agents/workers.md +0 -1034
- 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
- package/runtime/CONTEXT.md +0 -160
- package/runtime/QUEEN_ANT_ARCHITECTURE.md +0 -402
- package/runtime/aether-utils.sh +0 -3879
- package/runtime/data/signatures.json +0 -41
- package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +0 -1343
- package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +0 -2642
- package/runtime/docs/PHEROMONE-INJECTION.md +0 -240
- package/runtime/docs/PHEROMONE-INTEGRATION.md +0 -192
- package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +0 -426
- package/runtime/docs/README.md +0 -94
- package/runtime/docs/VISUAL-OUTPUT-SPEC.md +0 -219
- package/runtime/docs/biological-reference.md +0 -272
- package/runtime/docs/codebase-review.md +0 -399
- package/runtime/docs/command-sync.md +0 -164
- package/runtime/docs/constraints.md +0 -116
- package/runtime/docs/implementation-learnings.md +0 -89
- package/runtime/docs/known-issues.md +0 -217
- package/runtime/docs/namespace.md +0 -148
- package/runtime/docs/pathogen-schema-example.json +0 -36
- package/runtime/docs/pathogen-schema.md +0 -111
- package/runtime/docs/planning-discipline.md +0 -159
- package/runtime/docs/progressive-disclosure.md +0 -184
- package/runtime/lib/queen-utils.sh +0 -729
- package/runtime/model-profiles.yaml +0 -100
- package/runtime/planning.md +0 -159
- package/runtime/recover.sh +0 -136
- package/runtime/templates/QUEEN.md.template +0 -79
- package/runtime/utils/chamber-utils.sh +0 -285
- package/runtime/utils/file-lock.sh +0 -122
- package/runtime/utils/spawn-tree.sh +0 -428
- package/runtime/utils/spawn-with-model.sh +0 -56
- package/runtime/utils/xml-utils.sh +0 -2196
- package/runtime/workers-new-castes.md +0 -516
- /package/{runtime → .aether/docs/disciplines}/coding-standards.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/debugging.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/learning.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/tdd.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/verification-loop.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/verification.md +0 -0
- /package/{runtime → .aether}/schemas/aether-types.xsd +0 -0
- /package/{runtime → .aether}/schemas/colony-registry.xsd +0 -0
- /package/{runtime → .aether}/schemas/pheromone.xsd +0 -0
- /package/{runtime → .aether}/schemas/prompt.xsd +0 -0
- /package/{runtime → .aether}/schemas/queen-wisdom.xsd +0 -0
- /package/{runtime → .aether}/schemas/worker-priming.xsd +0 -0
- /package/{runtime → .aether}/utils/colorize-log.sh +0 -0
- /package/{runtime → .aether}/utils/queen-to-md.xsl +0 -0
package/runtime/aether-utils.sh
DELETED
|
@@ -1,3879 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Aether Colony Utility Layer
|
|
3
|
-
# Single entry point for deterministic colony operations
|
|
4
|
-
#
|
|
5
|
-
# Usage: bash .aether/aether-utils.sh <subcommand> [args...]
|
|
6
|
-
#
|
|
7
|
-
# All subcommands output JSON to stdout.
|
|
8
|
-
# Non-zero exit on error with JSON error message to stderr.
|
|
9
|
-
|
|
10
|
-
set -euo pipefail
|
|
11
|
-
|
|
12
|
-
# Set up structured error handling for unexpected failures
|
|
13
|
-
# This works alongside set -e but provides better context (line number, command)
|
|
14
|
-
# The error_handler function is defined in error-handler.sh if sourced
|
|
15
|
-
trap 'if type error_handler &>/dev/null; then error_handler ${LINENO} "$BASH_COMMAND" $?; fi' ERR
|
|
16
|
-
|
|
17
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
-
AETHER_ROOT="$(cd "$SCRIPT_DIR/.." && pwd 2>/dev/null || echo "$SCRIPT_DIR")"
|
|
19
|
-
DATA_DIR="$AETHER_ROOT/.aether/data"
|
|
20
|
-
|
|
21
|
-
# Initialize lock state before sourcing (file-lock.sh trap needs these)
|
|
22
|
-
LOCK_ACQUIRED=${LOCK_ACQUIRED:-false}
|
|
23
|
-
CURRENT_LOCK=${CURRENT_LOCK:-""}
|
|
24
|
-
|
|
25
|
-
# Source shared infrastructure if available
|
|
26
|
-
[[ -f "$SCRIPT_DIR/utils/file-lock.sh" ]] && source "$SCRIPT_DIR/utils/file-lock.sh"
|
|
27
|
-
[[ -f "$SCRIPT_DIR/utils/atomic-write.sh" ]] && source "$SCRIPT_DIR/utils/atomic-write.sh"
|
|
28
|
-
[[ -f "$SCRIPT_DIR/utils/error-handler.sh" ]] && source "$SCRIPT_DIR/utils/error-handler.sh"
|
|
29
|
-
[[ -f "$SCRIPT_DIR/utils/chamber-utils.sh" ]] && source "$SCRIPT_DIR/utils/chamber-utils.sh"
|
|
30
|
-
[[ -f "$SCRIPT_DIR/utils/xml-utils.sh" ]] && source "$SCRIPT_DIR/utils/xml-utils.sh"
|
|
31
|
-
|
|
32
|
-
# Fallback error constants if error-handler.sh wasn't sourced
|
|
33
|
-
# This prevents "unbound variable" errors in older installations
|
|
34
|
-
: "${E_UNKNOWN:=E_UNKNOWN}"
|
|
35
|
-
: "${E_HUB_NOT_FOUND:=E_HUB_NOT_FOUND}"
|
|
36
|
-
: "${E_REPO_NOT_INITIALIZED:=E_REPO_NOT_INITIALIZED}"
|
|
37
|
-
: "${E_FILE_NOT_FOUND:=E_FILE_NOT_FOUND}"
|
|
38
|
-
: "${E_JSON_INVALID:=E_JSON_INVALID}"
|
|
39
|
-
: "${E_LOCK_FAILED:=E_LOCK_FAILED}"
|
|
40
|
-
: "${E_GIT_ERROR:=E_GIT_ERROR}"
|
|
41
|
-
: "${E_VALIDATION_FAILED:=E_VALIDATION_FAILED}"
|
|
42
|
-
: "${E_FEATURE_UNAVAILABLE:=E_FEATURE_UNAVAILABLE}"
|
|
43
|
-
: "${E_BASH_ERROR:=E_BASH_ERROR}"
|
|
44
|
-
|
|
45
|
-
# Feature detection for graceful degradation
|
|
46
|
-
# These checks run silently - failures are logged but don't block operation
|
|
47
|
-
if type feature_disable &>/dev/null; then
|
|
48
|
-
# Check if DATA_DIR is writable for activity logging
|
|
49
|
-
[[ -w "$DATA_DIR" ]] 2>/dev/null || feature_disable "activity_log" "DATA_DIR not writable"
|
|
50
|
-
|
|
51
|
-
# Check if git is available for git integration
|
|
52
|
-
command -v git &>/dev/null || feature_disable "git_integration" "git not installed"
|
|
53
|
-
|
|
54
|
-
# Check if jq is available for JSON processing
|
|
55
|
-
command -v jq &>/dev/null || feature_disable "json_processing" "jq not installed"
|
|
56
|
-
|
|
57
|
-
# Check if lock utilities are available
|
|
58
|
-
[[ -f "$SCRIPT_DIR/utils/file-lock.sh" ]] || feature_disable "file_locking" "lock utilities not available"
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# Fallback atomic_write if not sourced (uses temp file + mv for true atomicity)
|
|
62
|
-
if ! type atomic_write &>/dev/null; then
|
|
63
|
-
atomic_write() {
|
|
64
|
-
local target="$1"
|
|
65
|
-
local content="$2"
|
|
66
|
-
local temp
|
|
67
|
-
temp=$(mktemp)
|
|
68
|
-
echo "$content" > "$temp"
|
|
69
|
-
mv "$temp" "$target"
|
|
70
|
-
}
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
# --- JSON output helpers ---
|
|
74
|
-
# Success: JSON to stdout, exit 0
|
|
75
|
-
json_ok() { printf '{"ok":true,"result":%s}\n' "$1"; }
|
|
76
|
-
|
|
77
|
-
# Error: JSON to stderr, exit 1
|
|
78
|
-
# Use enhanced json_err from error-handler.sh if available, otherwise fallback
|
|
79
|
-
if ! type json_err &>/dev/null; then
|
|
80
|
-
# Fallback: simple error format for backward compatibility
|
|
81
|
-
json_err() {
|
|
82
|
-
local message="${2:-$1}"
|
|
83
|
-
printf '{"ok":false,"error":"%s"}\n' "$message" >&2
|
|
84
|
-
exit 1
|
|
85
|
-
}
|
|
86
|
-
fi
|
|
87
|
-
|
|
88
|
-
# --- Caste emoji helper ---
|
|
89
|
-
get_caste_emoji() {
|
|
90
|
-
case "$1" in
|
|
91
|
-
*Queen*|*QUEEN*|*queen*) echo "👑🐜" ;;
|
|
92
|
-
*Builder*|*builder*|*Bolt*|*Hammer*|*Forge*|*Mason*|*Brick*|*Anvil*|*Weld*) echo "🔨🐜" ;;
|
|
93
|
-
*Watcher*|*watcher*|*Vigil*|*Sentinel*|*Guard*|*Keen*|*Sharp*|*Hawk*|*Alert*) echo "👁️🐜" ;;
|
|
94
|
-
*Scout*|*scout*|*Swift*|*Dash*|*Ranger*|*Track*|*Seek*|*Path*|*Roam*|*Quest*) echo "🔍🐜" ;;
|
|
95
|
-
*Colonizer*|*colonizer*|*Pioneer*|*Map*|*Chart*|*Venture*|*Explore*|*Compass*|*Atlas*|*Trek*) echo "🗺️🐜" ;;
|
|
96
|
-
*Surveyor*|*surveyor*|*Chart*|*Plot*|*Survey*|*Measure*|*Assess*|*Gauge*|*Sound*|*Fathom*) echo "📊🐜" ;;
|
|
97
|
-
*Architect*|*architect*|*Blueprint*|*Draft*|*Design*|*Plan*|*Schema*|*Frame*|*Sketch*|*Model*) echo "🏛️🐜" ;;
|
|
98
|
-
*Chaos*|*chaos*|*Probe*|*Stress*|*Shake*|*Twist*|*Snap*|*Breach*|*Surge*|*Jolt*) echo "🎲🐜" ;;
|
|
99
|
-
*Archaeologist*|*archaeologist*|*Relic*|*Fossil*|*Dig*|*Shard*|*Epoch*|*Strata*|*Lore*|*Glyph*) echo "🏺🐜" ;;
|
|
100
|
-
*Oracle*|*oracle*|*Sage*|*Seer*|*Vision*|*Augur*|*Mystic*|*Sibyl*|*Delph*|*Pythia*) echo "🔮🐜" ;;
|
|
101
|
-
*Route*|*route*) echo "📋🐜" ;;
|
|
102
|
-
*Ambassador*|*ambassador*|*Bridge*|*Connect*|*Link*|*Diplomat*|*Network*|*Protocol*) echo "🔌🐜" ;;
|
|
103
|
-
*Auditor*|*auditor*|*Review*|*Inspect*|*Examine*|*Scrutin*|*Critical*|*Verify*) echo "👥🐜" ;;
|
|
104
|
-
*Chronicler*|*chronicler*|*Document*|*Record*|*Write*|*Chronicle*|*Archive*|*Scribe*) echo "📝🐜" ;;
|
|
105
|
-
*Gatekeeper*|*gatekeeper*|*Guard*|*Protect*|*Secure*|*Shield*|*Depend*|*Supply*) echo "📦🐜" ;;
|
|
106
|
-
*Guardian*|*guardian*|*Defend*|*Patrol*|*Secure*|*Vigil*|*Watch*|*Safety*|*Security*) echo "🛡️🐜" ;;
|
|
107
|
-
*Includer*|*includer*|*Access*|*Inclusive*|*A11y*|*WCAG*|*Barrier*|*Universal*) echo "♿🐜" ;;
|
|
108
|
-
*Keeper*|*keeper*|*Archive*|*Store*|*Curate*|*Preserve*|*Knowledge*|*Wisdom*|*Pattern*) echo "📚🐜" ;;
|
|
109
|
-
*Measurer*|*measurer*|*Metric*|*Benchmark*|*Profile*|*Optimize*|*Performance*|*Speed*) echo "⚡🐜" ;;
|
|
110
|
-
*Probe*|*probe*|*Test*|*Excavat*|*Uncover*|*Edge*|*Case*|*Mutant*) echo "🧪🐜" ;;
|
|
111
|
-
*Tracker*|*tracker*|*Debug*|*Trace*|*Follow*|*Bug*|*Hunt*|*Root*) echo "🐛🐜" ;;
|
|
112
|
-
*Weaver*|*weaver*|*Refactor*|*Restruct*|*Transform*|*Clean*|*Pattern*|*Weave*) echo "🔄🐜" ;;
|
|
113
|
-
*) echo "🐜" ;;
|
|
114
|
-
esac
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
# ============================================
|
|
118
|
-
# CONTEXT UPDATE HELPER FUNCTION
|
|
119
|
-
# (Defined outside case block to fix SC2168: local outside function)
|
|
120
|
-
# ============================================
|
|
121
|
-
_cmd_context_update() {
|
|
122
|
-
local ctx_action="${1:-}"
|
|
123
|
-
local ctx_file="${AETHER_ROOT:-.}/.aether/CONTEXT.md"
|
|
124
|
-
local ctx_tmp="${ctx_file}.tmp"
|
|
125
|
-
local ctx_ts
|
|
126
|
-
ctx_ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
127
|
-
|
|
128
|
-
ensure_context_dir() {
|
|
129
|
-
local dir
|
|
130
|
-
dir=$(dirname "$ctx_file")
|
|
131
|
-
[[ -d "$dir" ]] || mkdir -p "$dir"
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
read_colony_state() {
|
|
135
|
-
local state_file="${AETHER_ROOT:-.}/.aether/data/COLONY_STATE.json"
|
|
136
|
-
if [[ -f "$state_file" ]]; then
|
|
137
|
-
current_phase=$(jq -r '.current_phase // "unknown"' "$state_file" 2>/dev/null)
|
|
138
|
-
milestone=$(jq -r '.milestone // "unknown"' "$state_file" 2>/dev/null)
|
|
139
|
-
goal=$(jq -r '.goal // ""' "$state_file" 2>/dev/null)
|
|
140
|
-
else
|
|
141
|
-
current_phase="unknown"
|
|
142
|
-
milestone="unknown"
|
|
143
|
-
goal=""
|
|
144
|
-
fi
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
case "$ctx_action" in
|
|
148
|
-
init)
|
|
149
|
-
local init_goal="${2:-}"
|
|
150
|
-
ensure_context_dir
|
|
151
|
-
read_colony_state
|
|
152
|
-
|
|
153
|
-
cat > "$ctx_file" << EOF
|
|
154
|
-
# Aether Colony — Current Context
|
|
155
|
-
|
|
156
|
-
> **This document is the colony's memory. If context collapses, read this file first.**
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## 🚦 System Status
|
|
161
|
-
|
|
162
|
-
| Field | Value |
|
|
163
|
-
|-------|-------|
|
|
164
|
-
| **Last Updated** | $ctx_ts |
|
|
165
|
-
| **Current Phase** | 1 |
|
|
166
|
-
| **Phase Name** | initialization |
|
|
167
|
-
| **Milestone** | First Mound |
|
|
168
|
-
| **Colony Status** | initializing |
|
|
169
|
-
| **Safe to Clear?** | ⚠️ NO — Colony just initialized |
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## 🎯 Current Goal
|
|
174
|
-
|
|
175
|
-
$init_goal
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
## 📍 What's In Progress
|
|
180
|
-
|
|
181
|
-
Colony initialization in progress...
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## ⚠️ Active Constraints (REDIRECT Signals)
|
|
186
|
-
|
|
187
|
-
| Constraint | Source | Date Set |
|
|
188
|
-
|------------|--------|----------|
|
|
189
|
-
| In the Aether repo, \`.aether/\` IS the source of truth — \`runtime/\` is auto-populated on publish | CLAUDE.md | Permanent |
|
|
190
|
-
| Never push without explicit user approval | CLAUDE.md Safety | Permanent |
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## 💭 Active Pheromones (FOCUS Signals)
|
|
195
|
-
|
|
196
|
-
*None active*
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## 📝 Recent Decisions
|
|
201
|
-
|
|
202
|
-
| Date | Decision | Rationale | Made By |
|
|
203
|
-
|------|----------|-----------|---------|
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## 📊 Recent Activity (Last 10 Actions)
|
|
208
|
-
|
|
209
|
-
| Timestamp | Command | Result | Files Changed |
|
|
210
|
-
|-----------|---------|--------|---------------|
|
|
211
|
-
| $ctx_ts | init | Colony initialized | — |
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## 🔄 Next Steps
|
|
216
|
-
|
|
217
|
-
1. Run \`/ant:plan\` to generate phases for the goal
|
|
218
|
-
2. Run \`/ant:build 1\` to start building
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## 🆘 If Context Collapses
|
|
223
|
-
|
|
224
|
-
**READ THIS SECTION FIRST**
|
|
225
|
-
|
|
226
|
-
### Immediate Recovery
|
|
227
|
-
|
|
228
|
-
1. **Read this file** — You're looking at it. Good.
|
|
229
|
-
2. **Check git status** — \`git status\` and \`git log --oneline -5\`
|
|
230
|
-
3. **Verify COLONY_STATE.json** — \`cat .aether/data/COLONY_STATE.json | jq .current_phase\`
|
|
231
|
-
4. **Resume work** — Continue from "Next Steps" above
|
|
232
|
-
|
|
233
|
-
### What We Were Doing
|
|
234
|
-
|
|
235
|
-
Colony was just initialized with goal: $init_goal
|
|
236
|
-
|
|
237
|
-
### Is It Safe to Continue?
|
|
238
|
-
|
|
239
|
-
- ✅ Colony is initialized
|
|
240
|
-
- ⚠️ No work completed yet
|
|
241
|
-
- ✅ All state in COLONY_STATE.json
|
|
242
|
-
|
|
243
|
-
**You can proceed safely.**
|
|
244
|
-
|
|
245
|
-
---
|
|
246
|
-
|
|
247
|
-
## 🐜 Colony Health
|
|
248
|
-
|
|
249
|
-
\`\`\`
|
|
250
|
-
Milestone: First Mound ░░░░░░░░░░ 0%
|
|
251
|
-
Phase: 1 ░░░░░░░░░░ initializing
|
|
252
|
-
Context: Active ░░░░░░░░░░ 0%
|
|
253
|
-
Git Commits: 0
|
|
254
|
-
\`\`\`
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
*This document updates automatically with every ant command. If you see old timestamps, run \`/ant:status\` to refresh.*
|
|
259
|
-
|
|
260
|
-
**Colony Memory Active** 🧠🐜
|
|
261
|
-
EOF
|
|
262
|
-
json_ok "{\"updated\":true,\"action\":\"init\",\"file\":\"$ctx_file\"}"
|
|
263
|
-
;;
|
|
264
|
-
|
|
265
|
-
update-phase)
|
|
266
|
-
local new_phase="${2:-}"
|
|
267
|
-
local new_phase_name="${3:-}"
|
|
268
|
-
local safe_clear="${4:-NO}"
|
|
269
|
-
local safe_reason="${5:-Phase in progress}"
|
|
270
|
-
|
|
271
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found. Run context-update init first."; }
|
|
272
|
-
|
|
273
|
-
sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
274
|
-
sed -i.bak "s/| \*\*Current Phase\*\* | .*/| **Current Phase** | $new_phase |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
275
|
-
sed -i.bak "s/| \*\*Phase Name\*\* | .*/| **Phase Name** | $new_phase_name |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
276
|
-
sed -i.bak "s/| \*\*Safe to Clear?\*\* | .*/| **Safe to Clear?** | $safe_clear — $safe_reason |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
277
|
-
|
|
278
|
-
json_ok "{\"updated\":true,\"action\":\"update-phase\",\"phase\":$new_phase}"
|
|
279
|
-
;;
|
|
280
|
-
|
|
281
|
-
activity)
|
|
282
|
-
local cmd="${2:-}"
|
|
283
|
-
local result="${3:-}"
|
|
284
|
-
local files_changed="${4:-—}"
|
|
285
|
-
|
|
286
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
287
|
-
|
|
288
|
-
sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
289
|
-
|
|
290
|
-
local activity_line="| $ctx_ts | $cmd | $result | $files_changed |"
|
|
291
|
-
|
|
292
|
-
awk -v line="$activity_line" '
|
|
293
|
-
/\| Timestamp \| Command \| Result \| Files Changed \|/ {
|
|
294
|
-
print
|
|
295
|
-
getline
|
|
296
|
-
print
|
|
297
|
-
print line
|
|
298
|
-
next
|
|
299
|
-
}
|
|
300
|
-
/^## 🆘 If Context Collapses/ { exit }
|
|
301
|
-
{ print }
|
|
302
|
-
' "$ctx_file" > "$ctx_tmp"
|
|
303
|
-
|
|
304
|
-
mv "$ctx_tmp" "$ctx_file"
|
|
305
|
-
json_ok "{\"updated\":true,\"action\":\"activity\",\"command\":\"$cmd\"}"
|
|
306
|
-
;;
|
|
307
|
-
|
|
308
|
-
safe-to-clear)
|
|
309
|
-
local safe="${2:-NO}"
|
|
310
|
-
local reason="${3:-Unknown state}"
|
|
311
|
-
|
|
312
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
313
|
-
|
|
314
|
-
sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
315
|
-
sed -i.bak "s/| \*\*Safe to Clear?\*\* | .*/| **Safe to Clear?** | $safe — $reason |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
316
|
-
|
|
317
|
-
json_ok "{\"updated\":true,\"action\":\"safe-to-clear\",\"safe\":\"$safe\"}"
|
|
318
|
-
;;
|
|
319
|
-
|
|
320
|
-
constraint)
|
|
321
|
-
local c_type="${2:-}"
|
|
322
|
-
local c_message="${3:-}"
|
|
323
|
-
local c_source="${4:-User}"
|
|
324
|
-
|
|
325
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
326
|
-
|
|
327
|
-
sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
328
|
-
|
|
329
|
-
if [[ "$c_type" == "redirect" ]]; then
|
|
330
|
-
sed -i.bak "/^## ⚠️ Active Constraints/,/^## /{ /^| Constraint |/a\\
|
|
331
|
-
| $c_message | $c_source | $ctx_ts |
|
|
332
|
-
}" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
333
|
-
elif [[ "$c_type" == "focus" ]]; then
|
|
334
|
-
sed -i.bak "/^## 💭 Active Pheromones/,/^## /{ /^| Signal |/a\\
|
|
335
|
-
| FOCUS | $c_message | normal |
|
|
336
|
-
}" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
337
|
-
fi
|
|
338
|
-
|
|
339
|
-
json_ok "{\"updated\":true,\"action\":\"constraint\",\"type\":\"$c_type\"}"
|
|
340
|
-
;;
|
|
341
|
-
|
|
342
|
-
decision)
|
|
343
|
-
local decision="${2:-}"
|
|
344
|
-
local rationale="${3:-}"
|
|
345
|
-
local made_by="${4:-Colony}"
|
|
346
|
-
|
|
347
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
348
|
-
|
|
349
|
-
sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
350
|
-
|
|
351
|
-
local decision_line="| $(echo $ctx_ts | cut -dT -f1) | $decision | $rationale | $made_by |"
|
|
352
|
-
|
|
353
|
-
awk -v line="$decision_line" '
|
|
354
|
-
/^## 📝 Recent Decisions/ { in_section=1 }
|
|
355
|
-
in_section && /^\| [0-9]{4}-[0-9]{2}-[0-9]{2} / { last_decision=NR }
|
|
356
|
-
in_section && /^## 📊 Recent Activity/ { in_section=0 }
|
|
357
|
-
{ lines[NR] = $0 }
|
|
358
|
-
END {
|
|
359
|
-
for (i=1; i<=NR; i++) {
|
|
360
|
-
if (i == last_decision) {
|
|
361
|
-
print lines[i]
|
|
362
|
-
print line
|
|
363
|
-
} else {
|
|
364
|
-
print lines[i]
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
' "$ctx_file" > "$ctx_tmp"
|
|
369
|
-
|
|
370
|
-
mv "$ctx_tmp" "$ctx_file"
|
|
371
|
-
json_ok "{\"updated\":true,\"action\":\"decision\"}"
|
|
372
|
-
;;
|
|
373
|
-
|
|
374
|
-
build-start)
|
|
375
|
-
local phase_id="${2:-}"
|
|
376
|
-
local worker_count="${3:-0}"
|
|
377
|
-
local tasks_count="${4:-0}"
|
|
378
|
-
|
|
379
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
380
|
-
|
|
381
|
-
sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
382
|
-
sed -i.bak "s/## 📍 What's In Progress/## 📍 What's In Progress\n\n**Phase $phase_id Build IN PROGRESS**\n- Workers: $worker_count | Tasks: $tasks_count\n- Started: $ctx_ts/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
383
|
-
sed -i.bak "s/| \*\*Safe to Clear?\*\* | .*/| **Safe to Clear?** | ⚠️ NO — Build in progress |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
384
|
-
|
|
385
|
-
json_ok "{\"updated\":true,\"action\":\"build-start\",\"workers\":$worker_count}"
|
|
386
|
-
;;
|
|
387
|
-
|
|
388
|
-
worker-spawn)
|
|
389
|
-
local ant_name="${2:-}"
|
|
390
|
-
local caste="${3:-}"
|
|
391
|
-
local task="${4:-}"
|
|
392
|
-
|
|
393
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
394
|
-
|
|
395
|
-
awk -v ant="$ant_name" -v caste="$caste" -v task="$task" -v ts="$ctx_ts" '
|
|
396
|
-
/^## 📍 What'\''s In Progress/ { in_progress=1 }
|
|
397
|
-
in_progress && /^## / && $0 !~ /What'\''s In Progress/ { in_progress=0 }
|
|
398
|
-
in_progress && /Workers:/ {
|
|
399
|
-
print
|
|
400
|
-
print " - " ts ": Spawned " ant " (" caste ") for: " task
|
|
401
|
-
next
|
|
402
|
-
}
|
|
403
|
-
{ print }
|
|
404
|
-
' "$ctx_file" > "$ctx_tmp" && mv "$ctx_tmp" "$ctx_file"
|
|
405
|
-
|
|
406
|
-
json_ok "{\"updated\":true,\"action\":\"worker-spawn\",\"ant\":\"$ant_name\"}"
|
|
407
|
-
;;
|
|
408
|
-
|
|
409
|
-
worker-complete)
|
|
410
|
-
local ant_name="${2:-}"
|
|
411
|
-
local status="${3:-completed}"
|
|
412
|
-
|
|
413
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
414
|
-
|
|
415
|
-
sed -i.bak "s/- .*$ant_name .*$/- $ant_name: $status (updated $ctx_ts)/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
416
|
-
|
|
417
|
-
json_ok "{\"updated\":true,\"action\":\"worker-complete\",\"ant\":\"$ant_name\"}"
|
|
418
|
-
;;
|
|
419
|
-
|
|
420
|
-
build-progress)
|
|
421
|
-
local completed="${2:-0}"
|
|
422
|
-
local total="${3:-1}"
|
|
423
|
-
local percentage=$(( completed * 100 / total ))
|
|
424
|
-
|
|
425
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
426
|
-
|
|
427
|
-
sed -i.bak "s/Build IN PROGRESS/Build IN PROGRESS ($percentage% complete)/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
428
|
-
|
|
429
|
-
json_ok "{\"updated\":true,\"action\":\"build-progress\",\"percent\":$percentage}"
|
|
430
|
-
;;
|
|
431
|
-
|
|
432
|
-
build-complete)
|
|
433
|
-
local status="${2:-completed}"
|
|
434
|
-
local result="${3:-success}"
|
|
435
|
-
|
|
436
|
-
[[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
|
|
437
|
-
|
|
438
|
-
sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
439
|
-
|
|
440
|
-
awk -v status="$status" -v result="$result" '
|
|
441
|
-
/^## 📍 What'\''s In Progress/ { in_progress=1 }
|
|
442
|
-
in_progress && /^## / && $0 !~ /What'\''s In Progress/ { in_progress=0 }
|
|
443
|
-
in_progress && /Build IN PROGRESS/ {
|
|
444
|
-
print "## 📍 What'\''s In Progress"
|
|
445
|
-
print ""
|
|
446
|
-
print "**Build " status "** — " result
|
|
447
|
-
next
|
|
448
|
-
}
|
|
449
|
-
in_progress { next }
|
|
450
|
-
{ print }
|
|
451
|
-
' "$ctx_file" > "$ctx_tmp" && mv "$ctx_tmp" "$ctx_file"
|
|
452
|
-
|
|
453
|
-
sed -i.bak "s/| \*\*Safe to Clear?\*\* | .*/| **Safe to Clear?** | ✅ YES — Build $status |/" "$ctx_file" && rm -f "$ctx_file.bak"
|
|
454
|
-
|
|
455
|
-
json_ok "{\"updated\":true,\"action\":\"build-complete\",\"status\":\"$status\"}"
|
|
456
|
-
;;
|
|
457
|
-
|
|
458
|
-
*)
|
|
459
|
-
json_err "$E_VALIDATION_FAILED" "Unknown context action: $ctx_action"
|
|
460
|
-
;;
|
|
461
|
-
esac
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
# --- Subcommand dispatch ---
|
|
465
|
-
cmd="${1:-help}"
|
|
466
|
-
shift 2>/dev/null || true
|
|
467
|
-
|
|
468
|
-
case "$cmd" in
|
|
469
|
-
help)
|
|
470
|
-
cat <<'EOF'
|
|
471
|
-
{"ok":true,"commands":["help","version","validate-state","load-state","unload-state","error-add","error-pattern-check","error-summary","activity-log","activity-log-init","activity-log-read","learning-promote","learning-inject","generate-ant-name","spawn-log","spawn-complete","spawn-can-spawn","spawn-get-depth","spawn-tree-load","spawn-tree-active","spawn-tree-depth","update-progress","check-antipattern","error-flag-pattern","signature-scan","signature-match","flag-add","flag-check-blockers","flag-resolve","flag-acknowledge","flag-list","flag-auto-resolve","autofix-checkpoint","autofix-rollback","spawn-can-spawn-swarm","swarm-findings-init","swarm-findings-add","swarm-findings-read","swarm-solution-set","swarm-cleanup","swarm-activity-log","swarm-display-init","swarm-display-update","swarm-display-get","swarm-timing-start","swarm-timing-get","swarm-timing-eta","view-state-init","view-state-get","view-state-set","view-state-toggle","view-state-expand","view-state-collapse","grave-add","grave-check","generate-commit-message","version-check","registry-add","bootstrap-system","model-profile","model-get","model-list","chamber-create","chamber-verify","chamber-list","milestone-detect","queen-init","queen-read","queen-promote","survey-load","survey-verify","pheromone-export"],"description":"Aether Colony Utility Layer — deterministic ops for the ant colony"}
|
|
472
|
-
EOF
|
|
473
|
-
;;
|
|
474
|
-
version)
|
|
475
|
-
json_ok '"1.0.0"'
|
|
476
|
-
;;
|
|
477
|
-
validate-state)
|
|
478
|
-
case "${1:-}" in
|
|
479
|
-
colony)
|
|
480
|
-
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
481
|
-
json_ok "$(jq '
|
|
482
|
-
def chk(f;t): if has(f) then (if (.[f]|type) as $a | t | any(. == $a) then "pass" else "fail: \(f) is \(.[f]|type), expected \(t|join("|"))" end) else "fail: missing \(f)" end;
|
|
483
|
-
def opt(f;t): if has(f) then (if (.[f]|type) as $a | t | any(. == $a) then "pass" else "fail: \(f) is \(.[f]|type), expected \(t|join("|"))" end) else "pass" end;
|
|
484
|
-
{file:"COLONY_STATE.json", checks:[
|
|
485
|
-
chk("goal";["null","string"]),
|
|
486
|
-
chk("state";["string"]),
|
|
487
|
-
chk("current_phase";["number"]),
|
|
488
|
-
chk("plan";["object"]),
|
|
489
|
-
chk("memory";["object"]),
|
|
490
|
-
chk("errors";["object"]),
|
|
491
|
-
chk("events";["array"]),
|
|
492
|
-
opt("session_id";["string","null"]),
|
|
493
|
-
opt("initialized_at";["string","null"]),
|
|
494
|
-
opt("build_started_at";["string","null"])
|
|
495
|
-
]} | . + {pass: ([.checks[] | select(. == "pass")] | length) == (.checks | length)}
|
|
496
|
-
' "$DATA_DIR/COLONY_STATE.json")"
|
|
497
|
-
;;
|
|
498
|
-
constraints)
|
|
499
|
-
[[ -f "$DATA_DIR/constraints.json" ]] || json_err "$E_FILE_NOT_FOUND" "constraints.json not found" '{"file":"constraints.json"}'
|
|
500
|
-
json_ok "$(jq '
|
|
501
|
-
def arr(f): if has(f) and (.[f]|type) == "array" then "pass" else "fail: \(f) not array" end;
|
|
502
|
-
{file:"constraints.json", checks:[
|
|
503
|
-
arr("focus"),
|
|
504
|
-
arr("constraints")
|
|
505
|
-
]} | . + {pass: ([.checks[] | select(. == "pass")] | length) == (.checks | length)}
|
|
506
|
-
' "$DATA_DIR/constraints.json")"
|
|
507
|
-
;;
|
|
508
|
-
all)
|
|
509
|
-
results=()
|
|
510
|
-
for target in colony constraints; do
|
|
511
|
-
results+=("$(bash "$SCRIPT_DIR/aether-utils.sh" validate-state "$target" 2>/dev/null || echo '{"ok":false}')")
|
|
512
|
-
done
|
|
513
|
-
combined=$(printf '%s\n' "${results[@]}" | jq -s '[.[] | .result // {file:"unknown",pass:false}]')
|
|
514
|
-
all_pass=$(echo "$combined" | jq 'all(.pass)')
|
|
515
|
-
json_ok "{\"pass\":$all_pass,\"files\":$combined}"
|
|
516
|
-
;;
|
|
517
|
-
*)
|
|
518
|
-
json_err "$E_VALIDATION_FAILED" "Usage: validate-state colony|constraints|all"
|
|
519
|
-
;;
|
|
520
|
-
esac
|
|
521
|
-
;;
|
|
522
|
-
error-add)
|
|
523
|
-
[[ $# -ge 3 ]] || json_err "$E_VALIDATION_FAILED" "Usage: error-add <category> <severity> <description> [phase]"
|
|
524
|
-
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
525
|
-
id="err_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
|
|
526
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
527
|
-
phase_val="${4:-null}"
|
|
528
|
-
if [[ "$phase_val" =~ ^[0-9]+$ ]]; then
|
|
529
|
-
phase_jq="$phase_val"
|
|
530
|
-
else
|
|
531
|
-
phase_jq="null"
|
|
532
|
-
fi
|
|
533
|
-
updated=$(jq --arg id "$id" --arg cat "$1" --arg sev "$2" --arg desc "$3" --argjson phase "$phase_jq" --arg ts "$ts" '
|
|
534
|
-
.errors.records += [{id:$id, category:$cat, severity:$sev, description:$desc, root_cause:null, phase:$phase, task_id:null, timestamp:$ts}] |
|
|
535
|
-
if (.errors.records|length) > 50 then .errors.records = .errors.records[-50:] else . end
|
|
536
|
-
' "$DATA_DIR/COLONY_STATE.json") || json_err "$E_JSON_INVALID" "Failed to update COLONY_STATE.json"
|
|
537
|
-
atomic_write "$DATA_DIR/COLONY_STATE.json" "$updated"
|
|
538
|
-
json_ok "\"$id\""
|
|
539
|
-
;;
|
|
540
|
-
error-pattern-check)
|
|
541
|
-
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
542
|
-
json_ok "$(jq '
|
|
543
|
-
.errors.records | group_by(.category) | map(select(length >= 3) |
|
|
544
|
-
{category: .[0].category, count: length,
|
|
545
|
-
first_seen: (sort_by(.timestamp) | first.timestamp),
|
|
546
|
-
last_seen: (sort_by(.timestamp) | last.timestamp)})
|
|
547
|
-
' "$DATA_DIR/COLONY_STATE.json")"
|
|
548
|
-
;;
|
|
549
|
-
error-summary)
|
|
550
|
-
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
551
|
-
json_ok "$(jq '{
|
|
552
|
-
total: (.errors.records | length),
|
|
553
|
-
by_category: (.errors.records | group_by(.category) | map({key: .[0].category, value: length}) | from_entries),
|
|
554
|
-
by_severity: (.errors.records | group_by(.severity) | map({key: .[0].severity, value: length}) | from_entries)
|
|
555
|
-
}' "$DATA_DIR/COLONY_STATE.json")"
|
|
556
|
-
;;
|
|
557
|
-
activity-log)
|
|
558
|
-
# Usage: activity-log <action> <caste_or_name> <description>
|
|
559
|
-
# The caste_or_name can be: "Builder", "Hammer-42 (Builder)", etc.
|
|
560
|
-
action="${1:-}"
|
|
561
|
-
caste="${2:-}"
|
|
562
|
-
description="${3:-}"
|
|
563
|
-
[[ -z "$action" || -z "$caste" || -z "$description" ]] && json_err "$E_VALIDATION_FAILED" "Usage: activity-log <action> <caste_or_name> <description>"
|
|
564
|
-
|
|
565
|
-
# Graceful degradation: check if activity logging is enabled
|
|
566
|
-
if type feature_enabled &>/dev/null && ! feature_enabled "activity_log"; then
|
|
567
|
-
json_warn "W_DEGRADED" "Activity logging disabled: $(type _feature_reason &>/dev/null && _feature_reason activity_log || echo 'unknown')"
|
|
568
|
-
exit 0
|
|
569
|
-
fi
|
|
570
|
-
|
|
571
|
-
log_file="$DATA_DIR/activity.log"
|
|
572
|
-
mkdir -p "$DATA_DIR"
|
|
573
|
-
ts=$(date -u +"%H:%M:%S")
|
|
574
|
-
emoji=$(get_caste_emoji "$caste")
|
|
575
|
-
echo "[$ts] $emoji $action $caste: $description" >> "$log_file"
|
|
576
|
-
json_ok '"logged"'
|
|
577
|
-
;;
|
|
578
|
-
activity-log-init)
|
|
579
|
-
phase_num="${1:-}"
|
|
580
|
-
phase_name="${2:-}"
|
|
581
|
-
[[ -z "$phase_num" ]] && json_err "$E_VALIDATION_FAILED" "Usage: activity-log-init <phase_num> [phase_name]"
|
|
582
|
-
|
|
583
|
-
# Graceful degradation: check if activity logging is enabled
|
|
584
|
-
if type feature_enabled &>/dev/null && ! feature_enabled "activity_log"; then
|
|
585
|
-
json_warn "W_DEGRADED" "Activity logging disabled: $(type _feature_reason &>/dev/null && _feature_reason activity_log || echo 'unknown')"
|
|
586
|
-
exit 0
|
|
587
|
-
fi
|
|
588
|
-
log_file="$DATA_DIR/activity.log"
|
|
589
|
-
mkdir -p "$DATA_DIR"
|
|
590
|
-
archive_file="$DATA_DIR/activity-phase-${phase_num}.log"
|
|
591
|
-
# Copy current log to per-phase archive (preserve combined log intact)
|
|
592
|
-
if [ -f "$log_file" ] && [ -s "$log_file" ]; then
|
|
593
|
-
# Handle retry scenario: don't overwrite existing archive
|
|
594
|
-
if [ -f "$archive_file" ]; then
|
|
595
|
-
archive_file="$DATA_DIR/activity-phase-${phase_num}-$(date -u +%s).log"
|
|
596
|
-
fi
|
|
597
|
-
cp "$log_file" "$archive_file"
|
|
598
|
-
fi
|
|
599
|
-
# Append phase header to combined log (NOT truncate)
|
|
600
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
601
|
-
echo "" >> "$log_file"
|
|
602
|
-
echo "🐜 ═══════════════════════════════════════════════════" >> "$log_file"
|
|
603
|
-
echo " P H A S E $phase_num: ${phase_name:-unnamed}" >> "$log_file"
|
|
604
|
-
echo " $ts" >> "$log_file"
|
|
605
|
-
echo "═══════════════════════════════════════════════════ 🐜" >> "$log_file"
|
|
606
|
-
archived_flag="false"
|
|
607
|
-
[ -f "$archive_file" ] && archived_flag="true"
|
|
608
|
-
json_ok "{\"archived\":$archived_flag}"
|
|
609
|
-
;;
|
|
610
|
-
activity-log-read)
|
|
611
|
-
caste_filter="${1:-}"
|
|
612
|
-
|
|
613
|
-
# Graceful degradation: check if activity logging is enabled
|
|
614
|
-
if type feature_enabled &>/dev/null && ! feature_enabled "activity_log"; then
|
|
615
|
-
json_warn "W_DEGRADED" "Activity logging disabled: $(type _feature_reason &>/dev/null && _feature_reason activity_log || echo 'unknown')"
|
|
616
|
-
exit 0
|
|
617
|
-
fi
|
|
618
|
-
|
|
619
|
-
log_file="$DATA_DIR/activity.log"
|
|
620
|
-
[[ -f "$log_file" ]] || json_err "$E_FILE_NOT_FOUND" "activity.log not found" '{"file":"activity.log"}'
|
|
621
|
-
if [ -n "$caste_filter" ]; then
|
|
622
|
-
content=$(grep "$caste_filter" "$log_file" | tail -20)
|
|
623
|
-
else
|
|
624
|
-
content=$(cat "$log_file")
|
|
625
|
-
fi
|
|
626
|
-
json_ok "$(echo "$content" | jq -Rs '.')"
|
|
627
|
-
;;
|
|
628
|
-
learning-promote)
|
|
629
|
-
[[ $# -ge 3 ]] || json_err "$E_VALIDATION_FAILED" "Usage: learning-promote <content> <source_project> <source_phase> [tags]"
|
|
630
|
-
content="$1"
|
|
631
|
-
source_project="$2"
|
|
632
|
-
source_phase="$3"
|
|
633
|
-
tags="${4:-}"
|
|
634
|
-
|
|
635
|
-
mkdir -p "$DATA_DIR"
|
|
636
|
-
global_file="$DATA_DIR/learnings.json"
|
|
637
|
-
|
|
638
|
-
if [[ ! -f "$global_file" ]]; then
|
|
639
|
-
echo '{"learnings":[],"version":1}' > "$global_file"
|
|
640
|
-
fi
|
|
641
|
-
|
|
642
|
-
id="global_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
|
|
643
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
644
|
-
|
|
645
|
-
if [[ -n "$tags" ]]; then
|
|
646
|
-
tags_json=$(echo "$tags" | jq -R 'split(",")')
|
|
647
|
-
else
|
|
648
|
-
tags_json="[]"
|
|
649
|
-
fi
|
|
650
|
-
|
|
651
|
-
current_count=$(jq '.learnings | length' "$global_file")
|
|
652
|
-
if [[ $current_count -ge 50 ]]; then
|
|
653
|
-
json_ok "{\"promoted\":false,\"reason\":\"cap_reached\",\"current_count\":$current_count,\"cap\":50}"
|
|
654
|
-
exit 0
|
|
655
|
-
fi
|
|
656
|
-
|
|
657
|
-
updated=$(jq --arg id "$id" --arg content "$content" --arg sp "$source_project" \
|
|
658
|
-
--arg phase "$source_phase" --argjson tags "$tags_json" --arg ts "$ts" '
|
|
659
|
-
.learnings += [{
|
|
660
|
-
id: $id,
|
|
661
|
-
content: $content,
|
|
662
|
-
source_project: $sp,
|
|
663
|
-
source_phase: $phase,
|
|
664
|
-
tags: $tags,
|
|
665
|
-
promoted_at: $ts
|
|
666
|
-
}]
|
|
667
|
-
' "$global_file") || json_err "$E_JSON_INVALID" "Failed to update learnings.json"
|
|
668
|
-
|
|
669
|
-
echo "$updated" > "$global_file"
|
|
670
|
-
json_ok "{\"promoted\":true,\"id\":\"$id\",\"count\":$((current_count + 1)),\"cap\":50}"
|
|
671
|
-
;;
|
|
672
|
-
learning-inject)
|
|
673
|
-
[[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: learning-inject <tech_keywords_csv>"
|
|
674
|
-
keywords="$1"
|
|
675
|
-
|
|
676
|
-
global_file="$DATA_DIR/learnings.json"
|
|
677
|
-
|
|
678
|
-
if [[ ! -f "$global_file" ]]; then
|
|
679
|
-
json_ok '{"learnings":[],"count":0}'
|
|
680
|
-
exit 0
|
|
681
|
-
fi
|
|
682
|
-
|
|
683
|
-
json_ok "$(jq --arg kw "$keywords" '
|
|
684
|
-
($kw | split(",") | map(ascii_downcase | ltrimstr(" ") | rtrimstr(" "))) as $keywords |
|
|
685
|
-
.learnings | map(
|
|
686
|
-
select(
|
|
687
|
-
.tags as $tags |
|
|
688
|
-
($keywords | any(. as $k | $tags | any(ascii_downcase | contains($k))))
|
|
689
|
-
)
|
|
690
|
-
) | {learnings: ., count: length}
|
|
691
|
-
' "$global_file")"
|
|
692
|
-
;;
|
|
693
|
-
spawn-log)
|
|
694
|
-
# Usage: spawn-log <parent_id> <child_caste> <child_name> <task_summary> [model] [status]
|
|
695
|
-
parent_id="${1:-}"
|
|
696
|
-
child_caste="${2:-}"
|
|
697
|
-
child_name="${3:-}"
|
|
698
|
-
task_summary="${4:-}"
|
|
699
|
-
model="${5:-default}"
|
|
700
|
-
status="${6:-spawned}"
|
|
701
|
-
[[ -z "$parent_id" || -z "$child_caste" || -z "$task_summary" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-log <parent_id> <child_caste> <child_name> <task_summary> [model] [status]"
|
|
702
|
-
mkdir -p "$DATA_DIR"
|
|
703
|
-
ts=$(date -u +"%H:%M:%S")
|
|
704
|
-
ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
705
|
-
emoji=$(get_caste_emoji "$child_caste")
|
|
706
|
-
parent_emoji=$(get_caste_emoji "$parent_id")
|
|
707
|
-
# Log to activity log with spawn format, emojis, and model info
|
|
708
|
-
echo "[$ts] ⚡ SPAWN $parent_emoji $parent_id -> $emoji $child_name ($child_caste): $task_summary [model: $model]" >> "$DATA_DIR/activity.log"
|
|
709
|
-
# Log to spawn tree file for visualization (NEW FORMAT: includes model field)
|
|
710
|
-
echo "$ts_full|$parent_id|$child_caste|$child_name|$task_summary|$model|$status" >> "$DATA_DIR/spawn-tree.txt"
|
|
711
|
-
# Return emoji-formatted result for display
|
|
712
|
-
json_ok "\"⚡ $emoji $child_name spawned\""
|
|
713
|
-
;;
|
|
714
|
-
spawn-complete)
|
|
715
|
-
# Usage: spawn-complete <ant_name> <status> [summary]
|
|
716
|
-
ant_name="${1:-}"
|
|
717
|
-
status="${2:-completed}"
|
|
718
|
-
summary="${3:-}"
|
|
719
|
-
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-complete <ant_name> <status> [summary]"
|
|
720
|
-
mkdir -p "$DATA_DIR"
|
|
721
|
-
ts=$(date -u +"%H:%M:%S")
|
|
722
|
-
ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
723
|
-
emoji=$(get_caste_emoji "$ant_name")
|
|
724
|
-
status_icon="✅"
|
|
725
|
-
[[ "$status" == "failed" ]] && status_icon="❌"
|
|
726
|
-
[[ "$status" == "blocked" ]] && status_icon="🚫"
|
|
727
|
-
echo "[$ts] $status_icon $emoji $ant_name: $status${summary:+ - $summary}" >> "$DATA_DIR/activity.log"
|
|
728
|
-
# Update spawn tree
|
|
729
|
-
echo "$ts_full|$ant_name|$status|$summary" >> "$DATA_DIR/spawn-tree.txt"
|
|
730
|
-
# Return emoji-formatted result for display
|
|
731
|
-
json_ok "\"$status_icon $emoji $ant_name: ${summary:-$status}\""
|
|
732
|
-
;;
|
|
733
|
-
spawn-can-spawn)
|
|
734
|
-
# Check if spawning is allowed at given depth
|
|
735
|
-
# Usage: spawn-can-spawn <depth>
|
|
736
|
-
# Returns: {can_spawn: bool, depth: N, max_spawns: N, current_total: N}
|
|
737
|
-
depth="${1:-1}"
|
|
738
|
-
|
|
739
|
-
# Depth limits: 1→4 spawns, 2→2 spawns, 3+→0 spawns
|
|
740
|
-
if [[ $depth -eq 1 ]]; then
|
|
741
|
-
max_for_depth=4
|
|
742
|
-
elif [[ $depth -eq 2 ]]; then
|
|
743
|
-
max_for_depth=2
|
|
744
|
-
else
|
|
745
|
-
max_for_depth=0
|
|
746
|
-
fi
|
|
747
|
-
|
|
748
|
-
# Count current spawns in this session (from spawn-tree.txt)
|
|
749
|
-
current=0
|
|
750
|
-
if [[ -f "$DATA_DIR/spawn-tree.txt" ]]; then
|
|
751
|
-
current=$(grep -c "|spawned$" "$DATA_DIR/spawn-tree.txt" 2>/dev/null || echo 0)
|
|
752
|
-
fi
|
|
753
|
-
|
|
754
|
-
# Global cap of 10 workers per phase
|
|
755
|
-
global_cap=10
|
|
756
|
-
|
|
757
|
-
# Can spawn if: depth < 3 AND under global cap
|
|
758
|
-
if [[ $depth -lt 3 && $current -lt $global_cap ]]; then
|
|
759
|
-
can="true"
|
|
760
|
-
else
|
|
761
|
-
can="false"
|
|
762
|
-
fi
|
|
763
|
-
|
|
764
|
-
json_ok "{\"can_spawn\":$can,\"depth\":$depth,\"max_spawns\":$max_for_depth,\"current_total\":$current,\"global_cap\":$global_cap}"
|
|
765
|
-
;;
|
|
766
|
-
spawn-get-depth)
|
|
767
|
-
# Return depth for a given ant name by tracing spawn tree
|
|
768
|
-
# Usage: spawn-get-depth <ant_name>
|
|
769
|
-
# Queen = depth 0, Queen's spawns = depth 1, their spawns = depth 2, etc.
|
|
770
|
-
ant_name="${1:-Queen}"
|
|
771
|
-
|
|
772
|
-
if [[ "$ant_name" == "Queen" ]]; then
|
|
773
|
-
json_ok '{"ant":"Queen","depth":0}'
|
|
774
|
-
exit 0
|
|
775
|
-
fi
|
|
776
|
-
|
|
777
|
-
# Check if spawn tree exists
|
|
778
|
-
if [[ ! -f "$DATA_DIR/spawn-tree.txt" ]]; then
|
|
779
|
-
json_ok "{\"ant\":\"$ant_name\",\"depth\":1,\"found\":false}"
|
|
780
|
-
exit 0
|
|
781
|
-
fi
|
|
782
|
-
|
|
783
|
-
# Check if ant exists in spawn tree (gracefully handle missing ants)
|
|
784
|
-
if ! grep -q "|$ant_name|" "$DATA_DIR/spawn-tree.txt" 2>/dev/null; then
|
|
785
|
-
json_ok "{\"ant\":\"$ant_name\",\"depth\":1,\"found\":false}"
|
|
786
|
-
exit 0
|
|
787
|
-
fi
|
|
788
|
-
|
|
789
|
-
# Find the spawn record for this ant and trace parents
|
|
790
|
-
depth=1
|
|
791
|
-
current_ant="$ant_name"
|
|
792
|
-
|
|
793
|
-
# Find who spawned this ant (look for lines with |spawned)
|
|
794
|
-
while true; do
|
|
795
|
-
# Format: timestamp|parent|caste|child_name|task|spawned
|
|
796
|
-
parent=$(grep "|$current_ant|" "$DATA_DIR/spawn-tree.txt" 2>/dev/null | grep "|spawned$" | head -1 | cut -d'|' -f2 || echo "")
|
|
797
|
-
|
|
798
|
-
if [[ -z "$parent" || "$parent" == "Queen" ]]; then
|
|
799
|
-
break
|
|
800
|
-
fi
|
|
801
|
-
|
|
802
|
-
depth=$((depth + 1))
|
|
803
|
-
current_ant="$parent"
|
|
804
|
-
|
|
805
|
-
# Safety limit
|
|
806
|
-
if [[ $depth -gt 5 ]]; then
|
|
807
|
-
break
|
|
808
|
-
fi
|
|
809
|
-
done
|
|
810
|
-
|
|
811
|
-
json_ok "{\"ant\":\"$ant_name\",\"depth\":$depth,\"found\":true}"
|
|
812
|
-
;;
|
|
813
|
-
update-progress)
|
|
814
|
-
# Usage: update-progress <percent> <message> [phase] [total_phases]
|
|
815
|
-
percent="${1:-0}"
|
|
816
|
-
message="${2:-Working...}"
|
|
817
|
-
phase="${3:-1}"
|
|
818
|
-
total="${4:-1}"
|
|
819
|
-
mkdir -p "$DATA_DIR"
|
|
820
|
-
|
|
821
|
-
# Calculate bar width (30 chars)
|
|
822
|
-
bar_width=30
|
|
823
|
-
filled=$((percent * bar_width / 100))
|
|
824
|
-
empty=$((bar_width - filled))
|
|
825
|
-
|
|
826
|
-
# Build progress bar with ASCII
|
|
827
|
-
bar=""
|
|
828
|
-
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
829
|
-
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
830
|
-
|
|
831
|
-
# Spinner frames for animation
|
|
832
|
-
spinners=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
|
|
833
|
-
spin_idx=$(($(date +%s) % 10))
|
|
834
|
-
spinner="${spinners[$spin_idx]}"
|
|
835
|
-
|
|
836
|
-
# Status indicator
|
|
837
|
-
if [[ $percent -ge 100 ]]; then
|
|
838
|
-
status_icon="✅"
|
|
839
|
-
elif [[ $percent -ge 50 ]]; then
|
|
840
|
-
status_icon="🔨"
|
|
841
|
-
else
|
|
842
|
-
status_icon="$spinner"
|
|
843
|
-
fi
|
|
844
|
-
|
|
845
|
-
# Write progress file
|
|
846
|
-
cat > "$DATA_DIR/watch-progress.txt" << EOF
|
|
847
|
-
.-.
|
|
848
|
-
(o o) AETHER COLONY
|
|
849
|
-
| O | Progress
|
|
850
|
-
\`-\`
|
|
851
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
852
|
-
|
|
853
|
-
Phase: $phase / $total
|
|
854
|
-
|
|
855
|
-
[$bar] $percent%
|
|
856
|
-
|
|
857
|
-
$status_icon $message
|
|
858
|
-
|
|
859
|
-
Target: 95% confidence
|
|
860
|
-
|
|
861
|
-
EOF
|
|
862
|
-
json_ok "{\"percent\":$percent,\"phase\":$phase}"
|
|
863
|
-
;;
|
|
864
|
-
error-flag-pattern)
|
|
865
|
-
# Usage: error-flag-pattern <pattern_name> <description> [severity]
|
|
866
|
-
# Tracks recurring error patterns across sessions for colony learning
|
|
867
|
-
pattern_name="${1:-}"
|
|
868
|
-
description="${2:-}"
|
|
869
|
-
severity="${3:-warning}"
|
|
870
|
-
[[ -z "$pattern_name" || -z "$description" ]] && json_err "$E_VALIDATION_FAILED" "Usage: error-flag-pattern <pattern_name> <description> [severity]"
|
|
871
|
-
|
|
872
|
-
patterns_file="$DATA_DIR/error-patterns.json"
|
|
873
|
-
mkdir -p "$DATA_DIR"
|
|
874
|
-
|
|
875
|
-
if [[ ! -f "$patterns_file" ]]; then
|
|
876
|
-
echo '{"patterns":[],"version":1}' > "$patterns_file"
|
|
877
|
-
fi
|
|
878
|
-
|
|
879
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
880
|
-
project_name=$(basename "$PWD")
|
|
881
|
-
|
|
882
|
-
# Check if pattern already exists
|
|
883
|
-
existing=$(jq --arg name "$pattern_name" '.patterns[] | select(.name == $name)' "$patterns_file" 2>/dev/null)
|
|
884
|
-
|
|
885
|
-
if [[ -n "$existing" ]]; then
|
|
886
|
-
# Update existing pattern - increment count
|
|
887
|
-
updated=$(jq --arg name "$pattern_name" --arg ts "$ts" --arg proj "$project_name" '
|
|
888
|
-
.patterns = [.patterns[] | if .name == $name then
|
|
889
|
-
.occurrences += 1 |
|
|
890
|
-
.last_seen = $ts |
|
|
891
|
-
.projects = ((.projects + [$proj]) | unique)
|
|
892
|
-
else . end]
|
|
893
|
-
' "$patterns_file") || json_err "$E_JSON_INVALID" "Failed to update pattern"
|
|
894
|
-
echo "$updated" > "$patterns_file"
|
|
895
|
-
count=$(echo "$updated" | jq --arg name "$pattern_name" '.patterns[] | select(.name == $name) | .occurrences')
|
|
896
|
-
json_ok "{\"updated\":true,\"pattern\":\"$pattern_name\",\"occurrences\":$count}"
|
|
897
|
-
else
|
|
898
|
-
# Add new pattern
|
|
899
|
-
updated=$(jq --arg name "$pattern_name" --arg desc "$description" --arg sev "$severity" --arg ts "$ts" --arg proj "$project_name" '
|
|
900
|
-
.patterns += [{
|
|
901
|
-
"name": $name,
|
|
902
|
-
"description": $desc,
|
|
903
|
-
"severity": $sev,
|
|
904
|
-
"first_seen": $ts,
|
|
905
|
-
"last_seen": $ts,
|
|
906
|
-
"occurrences": 1,
|
|
907
|
-
"projects": [$proj],
|
|
908
|
-
"resolved": false
|
|
909
|
-
}]
|
|
910
|
-
' "$patterns_file") || json_err "$E_JSON_INVALID" "Failed to add pattern"
|
|
911
|
-
echo "$updated" > "$patterns_file"
|
|
912
|
-
json_ok "{\"created\":true,\"pattern\":\"$pattern_name\"}"
|
|
913
|
-
fi
|
|
914
|
-
;;
|
|
915
|
-
error-patterns-check)
|
|
916
|
-
# Check for known error patterns in a file or codebase
|
|
917
|
-
# Returns patterns that should be avoided
|
|
918
|
-
global_file="$DATA_DIR/error-patterns.json"
|
|
919
|
-
|
|
920
|
-
if [[ ! -f "$global_file" ]]; then
|
|
921
|
-
json_ok '{"patterns":[],"count":0}'
|
|
922
|
-
exit 0
|
|
923
|
-
fi
|
|
924
|
-
|
|
925
|
-
# Return patterns with 2+ occurrences (recurring issues)
|
|
926
|
-
json_ok "$(jq '{
|
|
927
|
-
patterns: [.patterns[] | select(.occurrences >= 2 and .resolved == false)],
|
|
928
|
-
count: ([.patterns[] | select(.occurrences >= 2 and .resolved == false)] | length)
|
|
929
|
-
}' "$global_file")"
|
|
930
|
-
;;
|
|
931
|
-
check-antipattern)
|
|
932
|
-
# Usage: check-antipattern <file_path>
|
|
933
|
-
# Returns JSON with critical issues and warnings
|
|
934
|
-
file_path="${1:-}"
|
|
935
|
-
[[ -z "$file_path" ]] && json_err "$E_VALIDATION_FAILED" "Usage: check-antipattern <file_path>"
|
|
936
|
-
[[ ! -f "$file_path" ]] && json_ok '{"critical":[],"warnings":[],"clean":true}'
|
|
937
|
-
|
|
938
|
-
criticals=()
|
|
939
|
-
warnings=()
|
|
940
|
-
|
|
941
|
-
# Detect file type
|
|
942
|
-
ext="${file_path##*.}"
|
|
943
|
-
|
|
944
|
-
case "$ext" in
|
|
945
|
-
swift)
|
|
946
|
-
# Swift didSet infinite recursion check
|
|
947
|
-
if grep -n "didSet" "$file_path" 2>/dev/null | grep -q "self\."; then
|
|
948
|
-
line=$(grep -n "didSet" "$file_path" | grep "self\." | head -1 | cut -d: -f1)
|
|
949
|
-
criticals+=("{\"pattern\":\"didSet-recursion\",\"file\":\"$file_path\",\"line\":$line,\"message\":\"Potential didSet infinite recursion - self assignment in didSet\"}")
|
|
950
|
-
fi
|
|
951
|
-
;;
|
|
952
|
-
ts|tsx|js|jsx)
|
|
953
|
-
# TypeScript any type check
|
|
954
|
-
if grep -nE '\bany\b' "$file_path" 2>/dev/null | grep -qv "//.*any"; then
|
|
955
|
-
count=$(grep -cE '\bany\b' "$file_path" 2>/dev/null || echo "0")
|
|
956
|
-
warnings+=("{\"pattern\":\"typescript-any\",\"file\":\"$file_path\",\"count\":$count,\"message\":\"Found $count uses of 'any' type\"}")
|
|
957
|
-
fi
|
|
958
|
-
# Console.log in production code (not in test files)
|
|
959
|
-
if [[ ! "$file_path" =~ \.test\. && ! "$file_path" =~ \.spec\. ]]; then
|
|
960
|
-
if grep -n "console\.log" "$file_path" 2>/dev/null | grep -qv "//"; then
|
|
961
|
-
count=$(grep -c "console\.log" "$file_path" 2>/dev/null || echo "0")
|
|
962
|
-
warnings+=("{\"pattern\":\"console-log\",\"file\":\"$file_path\",\"count\":$count,\"message\":\"Found $count console.log statements\"}")
|
|
963
|
-
fi
|
|
964
|
-
fi
|
|
965
|
-
;;
|
|
966
|
-
py)
|
|
967
|
-
# Python bare except
|
|
968
|
-
if grep -n "except:" "$file_path" 2>/dev/null | grep -qv "#"; then
|
|
969
|
-
line=$(grep -n "except:" "$file_path" | head -1 | cut -d: -f1)
|
|
970
|
-
warnings+=("{\"pattern\":\"bare-except\",\"file\":\"$file_path\",\"line\":$line,\"message\":\"Bare except clause - specify exception type\"}")
|
|
971
|
-
fi
|
|
972
|
-
;;
|
|
973
|
-
esac
|
|
974
|
-
|
|
975
|
-
# Common patterns across all languages
|
|
976
|
-
# Exposed secrets check (critical)
|
|
977
|
-
if grep -nE "(api_key|apikey|secret|password|token)\s*=\s*['\"][^'\"]+['\"]" "$file_path" 2>/dev/null | grep -qvi "example\|test\|mock\|fake"; then
|
|
978
|
-
line=$(grep -nE "(api_key|apikey|secret|password|token)\s*=\s*['\"]" "$file_path" | head -1 | cut -d: -f1)
|
|
979
|
-
criticals+=("{\"pattern\":\"exposed-secret\",\"file\":\"$file_path\",\"line\":${line:-0},\"message\":\"Potential hardcoded secret or credential\"}")
|
|
980
|
-
fi
|
|
981
|
-
|
|
982
|
-
# TODO/FIXME check (warning)
|
|
983
|
-
if grep -nE "(TODO|FIXME|XXX|HACK)" "$file_path" 2>/dev/null | head -1 | grep -q .; then
|
|
984
|
-
count=$(grep -cE "(TODO|FIXME|XXX|HACK)" "$file_path" 2>/dev/null || echo "0")
|
|
985
|
-
warnings+=("{\"pattern\":\"todo-comment\",\"file\":\"$file_path\",\"count\":$count,\"message\":\"Found $count TODO/FIXME comments\"}")
|
|
986
|
-
fi
|
|
987
|
-
|
|
988
|
-
# Build result JSON
|
|
989
|
-
crit_json="[]"
|
|
990
|
-
warn_json="[]"
|
|
991
|
-
if [[ ${#criticals[@]} -gt 0 ]]; then
|
|
992
|
-
crit_json=$(printf '%s\n' "${criticals[@]}" | jq -s '.')
|
|
993
|
-
fi
|
|
994
|
-
if [[ ${#warnings[@]} -gt 0 ]]; then
|
|
995
|
-
warn_json=$(printf '%s\n' "${warnings[@]}" | jq -s '.')
|
|
996
|
-
fi
|
|
997
|
-
|
|
998
|
-
clean="true"
|
|
999
|
-
[[ ${#criticals[@]} -gt 0 || ${#warnings[@]} -gt 0 ]] && clean="false"
|
|
1000
|
-
|
|
1001
|
-
json_ok "{\"critical\":$crit_json,\"warnings\":$warn_json,\"clean\":$clean}"
|
|
1002
|
-
;;
|
|
1003
|
-
signature-scan)
|
|
1004
|
-
# Scan a file for a signature pattern
|
|
1005
|
-
# Usage: signature-scan <target_file> <signature_name>
|
|
1006
|
-
# Returns matching signature details as JSON if found, empty result if no match
|
|
1007
|
-
# Exit code 0 if no match, 1 if match found
|
|
1008
|
-
target_file="${1:-}"
|
|
1009
|
-
signature_name="${2:-}"
|
|
1010
|
-
[[ -z "$target_file" || -z "$signature_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: signature-scan <target_file> <signature_name>"
|
|
1011
|
-
|
|
1012
|
-
# Handle missing target file gracefully
|
|
1013
|
-
if [[ ! -f "$target_file" ]]; then
|
|
1014
|
-
json_ok '{"found":false,"signature":null}'
|
|
1015
|
-
exit 0
|
|
1016
|
-
fi
|
|
1017
|
-
|
|
1018
|
-
# Read signature details from signatures.json
|
|
1019
|
-
signatures_file="$DATA_DIR/signatures.json"
|
|
1020
|
-
if [[ ! -f "$signatures_file" ]]; then
|
|
1021
|
-
json_ok '{"found":false,"signature":null}'
|
|
1022
|
-
exit 0
|
|
1023
|
-
fi
|
|
1024
|
-
|
|
1025
|
-
# Extract signature details using jq
|
|
1026
|
-
signature_data=$(jq --arg name "$signature_name" '.signatures[] | select(.name == $name)' "$signatures_file" 2>/dev/null)
|
|
1027
|
-
|
|
1028
|
-
if [[ -z "$signature_data" ]]; then
|
|
1029
|
-
# Signature not found in storage
|
|
1030
|
-
json_ok '{"found":false,"signature":null}'
|
|
1031
|
-
exit 0
|
|
1032
|
-
fi
|
|
1033
|
-
|
|
1034
|
-
# Extract pattern and confidence threshold
|
|
1035
|
-
pattern_string=$(echo "$signature_data" | jq -r '.pattern_string // empty')
|
|
1036
|
-
confidence_threshold=$(echo "$signature_data" | jq -r '.confidence_threshold // 0.8')
|
|
1037
|
-
|
|
1038
|
-
if [[ -z "$pattern_string" || "$pattern_string" == "null" ]]; then
|
|
1039
|
-
json_ok '{"found":false,"signature":null}'
|
|
1040
|
-
exit 0
|
|
1041
|
-
fi
|
|
1042
|
-
|
|
1043
|
-
# Use grep to search for the pattern in target file
|
|
1044
|
-
if grep -q -- "$pattern_string" "$target_file" 2>/dev/null; then
|
|
1045
|
-
# Match found - return signature details with match info
|
|
1046
|
-
match_count=$(grep -c -- "$pattern_string" "$target_file" 2>/dev/null || echo "1")
|
|
1047
|
-
json_ok "{\"found\":true,\"signature\":$signature_data,\"match_count\":$match_count}"
|
|
1048
|
-
exit 1
|
|
1049
|
-
else
|
|
1050
|
-
# No match
|
|
1051
|
-
json_ok '{"found":false,"signature":null}'
|
|
1052
|
-
exit 0
|
|
1053
|
-
fi
|
|
1054
|
-
;;
|
|
1055
|
-
signature-match)
|
|
1056
|
-
# Scan a directory for files matching high-confidence signatures
|
|
1057
|
-
# Usage: signature-match <directory> [file_pattern]
|
|
1058
|
-
# Returns results per file showing which signatures matched
|
|
1059
|
-
target_dir="${1:-}"
|
|
1060
|
-
file_pattern="${2:-}"
|
|
1061
|
-
# Set default pattern if not provided - avoid zsh brace expansion quirk by setting it explicitly
|
|
1062
|
-
if [[ -z "$file_pattern" ]]; then
|
|
1063
|
-
file_pattern="*"
|
|
1064
|
-
fi
|
|
1065
|
-
[[ -z "$target_dir" ]] && json_err "$E_VALIDATION_FAILED" "Usage: signature-match <directory> [file_pattern]"
|
|
1066
|
-
|
|
1067
|
-
# Validate directory exists
|
|
1068
|
-
[[ ! -d "$target_dir" ]] && json_err "$E_FILE_NOT_FOUND" "Directory not found: $target_dir"
|
|
1069
|
-
|
|
1070
|
-
# Path to signatures file
|
|
1071
|
-
signatures_file="$DATA_DIR/signatures.json"
|
|
1072
|
-
[[ ! -f "$signatures_file" ]] && json_err "$E_FILE_NOT_FOUND" "Signatures file not found"
|
|
1073
|
-
|
|
1074
|
-
# Read high-confidence signatures (confidence >= 0.7) using jq -c for compact single-line output
|
|
1075
|
-
high_conf_signatures=$(jq -c '.signatures[] | select(.confidence_threshold >= 0.7)' "$signatures_file" 2>/dev/null)
|
|
1076
|
-
|
|
1077
|
-
# Check if any high-confidence signatures exist
|
|
1078
|
-
sig_count=$(echo "$high_conf_signatures" | grep -c '{' || echo 0)
|
|
1079
|
-
if [[ "$sig_count" -eq 0 ]]; then
|
|
1080
|
-
json_ok '{"files_scanned":0,"matches":{},"signatures_checked":0}'
|
|
1081
|
-
exit 0
|
|
1082
|
-
fi
|
|
1083
|
-
|
|
1084
|
-
# Find all files to scan
|
|
1085
|
-
declare -a files=()
|
|
1086
|
-
if [[ -n "$file_pattern" ]]; then
|
|
1087
|
-
# User specified pattern - use it directly
|
|
1088
|
-
while IFS= read -r -d '' file; do
|
|
1089
|
-
files+=("$file")
|
|
1090
|
-
done < <(find "$target_dir" -type f -name "$file_pattern" -print0 2>/dev/null || true)
|
|
1091
|
-
else
|
|
1092
|
-
# Default: match common code file types
|
|
1093
|
-
while IFS= read -r -d '' file; do
|
|
1094
|
-
files+=("$file")
|
|
1095
|
-
done < <(find "$target_dir" -type f \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.sh" -o -name "*.txt" -o -name "*.md" \) -print0 2>/dev/null || true)
|
|
1096
|
-
fi
|
|
1097
|
-
|
|
1098
|
-
file_count=${#files[@]}
|
|
1099
|
-
|
|
1100
|
-
# If no files found, return empty result
|
|
1101
|
-
if [[ "$file_count" -eq 0 ]]; then
|
|
1102
|
-
json_ok "{\"files_scanned\":0,\"matches\":{},\"signatures_checked\":$sig_count}"
|
|
1103
|
-
exit 0
|
|
1104
|
-
fi
|
|
1105
|
-
|
|
1106
|
-
# Collect matches per file - process each file (bash 3.2 compatible: build JSON directly)
|
|
1107
|
-
matched_files="{}"
|
|
1108
|
-
|
|
1109
|
-
# Read signatures into array first (avoid subshell issues)
|
|
1110
|
-
sig_array=""
|
|
1111
|
-
while IFS= read -r sig_entry; do
|
|
1112
|
-
[[ -z "$sig_entry" ]] && continue
|
|
1113
|
-
sig_array="${sig_array}${sig_entry}"$'\n'
|
|
1114
|
-
done <<< "$high_conf_signatures"
|
|
1115
|
-
|
|
1116
|
-
for file in "${files[@]}"; do
|
|
1117
|
-
# For each file, check each signature - use process subst to avoid subshell
|
|
1118
|
-
file_key=$(basename "$file")
|
|
1119
|
-
matches_for_file="[]"
|
|
1120
|
-
|
|
1121
|
-
while IFS= read -r sig_entry; do
|
|
1122
|
-
[[ -z "$sig_entry" ]] && continue
|
|
1123
|
-
sig_name=$(echo "$sig_entry" | jq -r '.name')
|
|
1124
|
-
sig_pattern=$(echo "$sig_entry" | jq -r '.pattern_string')
|
|
1125
|
-
sig_conf=$(echo "$sig_entry" | jq -r '.confidence_threshold')
|
|
1126
|
-
sig_desc=$(echo "$sig_entry" | jq -r '.description')
|
|
1127
|
-
|
|
1128
|
-
# Skip if pattern is null/empty
|
|
1129
|
-
[[ -z "$sig_pattern" || "$sig_pattern" == "null" ]] && continue
|
|
1130
|
-
|
|
1131
|
-
# Check if pattern matches in file using grep
|
|
1132
|
-
if grep -q -- "$sig_pattern" "$file" 2>/dev/null; then
|
|
1133
|
-
match_count=$(grep -c -- "$sig_pattern" "$file" 2>/dev/null || echo "1")
|
|
1134
|
-
|
|
1135
|
-
# Add to results
|
|
1136
|
-
matches_for_file=$(echo "$matches_for_file" | jq --arg n "$sig_name" --arg d "$sig_desc" --argjson c "$sig_conf" --argjson m "$match_count" \
|
|
1137
|
-
'. += [{"name":$n,"description":$d,"confidence_threshold":$c,"match_count":$m}]')
|
|
1138
|
-
fi
|
|
1139
|
-
done < <(echo "$high_conf_signatures" | jq -c '.' 2>/dev/null || true)
|
|
1140
|
-
|
|
1141
|
-
# If any signatures matched, add to results
|
|
1142
|
-
sig_result_count=$(echo "$matches_for_file" | jq 'length')
|
|
1143
|
-
if [[ "$sig_result_count" -gt 0 ]]; then
|
|
1144
|
-
temp_result=$(mktemp)
|
|
1145
|
-
echo "$matched_files" | jq --arg k "$file_key" --argjson v "$matches_for_file" '. + {($k): $v}' > "$temp_result"
|
|
1146
|
-
matched_files=$(cat "$temp_result")
|
|
1147
|
-
rm -f "$temp_result"
|
|
1148
|
-
fi
|
|
1149
|
-
done
|
|
1150
|
-
|
|
1151
|
-
json_ok "{\"files_scanned\":$file_count,\"matches\":$matched_files,\"signatures_checked\":$sig_count}"
|
|
1152
|
-
;;
|
|
1153
|
-
flag-add)
|
|
1154
|
-
# Add a project-specific flag (blocker, issue, or note)
|
|
1155
|
-
# Usage: flag-add <type> <title> <description> [source] [phase]
|
|
1156
|
-
# Types: blocker (critical, blocks advancement), issue (high, warning), note (low, info)
|
|
1157
|
-
type="${1:-issue}"
|
|
1158
|
-
title="${2:-}"
|
|
1159
|
-
desc="${3:-}"
|
|
1160
|
-
source="${4:-manual}"
|
|
1161
|
-
phase="${5:-null}"
|
|
1162
|
-
[[ -z "$title" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-add <type> <title> <description> [source] [phase]"
|
|
1163
|
-
|
|
1164
|
-
mkdir -p "$DATA_DIR"
|
|
1165
|
-
flags_file="$DATA_DIR/flags.json"
|
|
1166
|
-
|
|
1167
|
-
if [[ ! -f "$flags_file" ]]; then
|
|
1168
|
-
echo '{"version":1,"flags":[]}' > "$flags_file"
|
|
1169
|
-
fi
|
|
1170
|
-
|
|
1171
|
-
id="flag_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
|
|
1172
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1173
|
-
|
|
1174
|
-
# Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
|
|
1175
|
-
lock_acquired=false
|
|
1176
|
-
if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
|
|
1177
|
-
json_warn "W_DEGRADED" "File locking disabled - proceeding without lock: $(type _feature_reason &>/dev/null && _feature_reason file_locking || echo 'unknown')"
|
|
1178
|
-
else
|
|
1179
|
-
acquire_lock "$flags_file" || {
|
|
1180
|
-
if type json_err &>/dev/null; then
|
|
1181
|
-
json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
|
|
1182
|
-
else
|
|
1183
|
-
echo '{"ok":false,"error":"Failed to acquire lock on flags.json"}' >&2
|
|
1184
|
-
exit 1
|
|
1185
|
-
fi
|
|
1186
|
-
}
|
|
1187
|
-
lock_acquired=true
|
|
1188
|
-
# Ensure lock is always released on exit (BUG-002 fix)
|
|
1189
|
-
trap 'release_lock "$flags_file" 2>/dev/null || true' EXIT
|
|
1190
|
-
fi
|
|
1191
|
-
|
|
1192
|
-
# Map type to severity
|
|
1193
|
-
case "$type" in
|
|
1194
|
-
blocker) severity="critical" ;;
|
|
1195
|
-
issue) severity="high" ;;
|
|
1196
|
-
note) severity="low" ;;
|
|
1197
|
-
*) severity="medium" ;;
|
|
1198
|
-
esac
|
|
1199
|
-
|
|
1200
|
-
# Handle phase as number or null
|
|
1201
|
-
if [[ "$phase" =~ ^[0-9]+$ ]]; then
|
|
1202
|
-
phase_jq="$phase"
|
|
1203
|
-
else
|
|
1204
|
-
phase_jq="null"
|
|
1205
|
-
fi
|
|
1206
|
-
|
|
1207
|
-
updated=$(jq --arg id "$id" --arg type "$type" --arg sev "$severity" \
|
|
1208
|
-
--arg title "$title" --arg desc "$desc" --arg source "$source" \
|
|
1209
|
-
--argjson phase "$phase_jq" --arg ts "$ts" '
|
|
1210
|
-
.flags += [{
|
|
1211
|
-
id: $id,
|
|
1212
|
-
type: $type,
|
|
1213
|
-
severity: $sev,
|
|
1214
|
-
title: $title,
|
|
1215
|
-
description: $desc,
|
|
1216
|
-
source: $source,
|
|
1217
|
-
phase: $phase,
|
|
1218
|
-
created_at: $ts,
|
|
1219
|
-
acknowledged_at: null,
|
|
1220
|
-
resolved_at: null,
|
|
1221
|
-
resolution: null,
|
|
1222
|
-
auto_resolve_on: (if $type == "blocker" and ($source | test("chaos") | not) then "build_pass" else null end)
|
|
1223
|
-
}]
|
|
1224
|
-
' "$flags_file") || { json_err "$E_JSON_INVALID" "Failed to add flag"; }
|
|
1225
|
-
|
|
1226
|
-
atomic_write "$flags_file" "$updated"
|
|
1227
|
-
# Lock released by trap on exit (BUG-002 fix)
|
|
1228
|
-
trap - EXIT
|
|
1229
|
-
json_ok "{\"id\":\"$id\",\"type\":\"$type\",\"severity\":\"$severity\"}"
|
|
1230
|
-
;;
|
|
1231
|
-
flag-check-blockers)
|
|
1232
|
-
# Count unresolved blockers for the current phase
|
|
1233
|
-
# Usage: flag-check-blockers [phase]
|
|
1234
|
-
phase="${1:-}"
|
|
1235
|
-
flags_file="$DATA_DIR/flags.json"
|
|
1236
|
-
|
|
1237
|
-
if [[ ! -f "$flags_file" ]]; then
|
|
1238
|
-
json_ok '{"blockers":0,"issues":0,"notes":0}'
|
|
1239
|
-
exit 0
|
|
1240
|
-
fi
|
|
1241
|
-
|
|
1242
|
-
if [[ -n "$phase" && "$phase" =~ ^[0-9]+$ ]]; then
|
|
1243
|
-
# Filter by phase
|
|
1244
|
-
result=$(jq --argjson phase "$phase" '{
|
|
1245
|
-
blockers: [.flags[] | select(.type == "blocker" and .resolved_at == null and (.phase == $phase or .phase == null))] | length,
|
|
1246
|
-
issues: [.flags[] | select(.type == "issue" and .resolved_at == null and (.phase == $phase or .phase == null))] | length,
|
|
1247
|
-
notes: [.flags[] | select(.type == "note" and .resolved_at == null and (.phase == $phase or .phase == null))] | length
|
|
1248
|
-
}' "$flags_file")
|
|
1249
|
-
else
|
|
1250
|
-
# All unresolved
|
|
1251
|
-
result=$(jq '{
|
|
1252
|
-
blockers: [.flags[] | select(.type == "blocker" and .resolved_at == null)] | length,
|
|
1253
|
-
issues: [.flags[] | select(.type == "issue" and .resolved_at == null)] | length,
|
|
1254
|
-
notes: [.flags[] | select(.type == "note" and .resolved_at == null)] | length
|
|
1255
|
-
}' "$flags_file")
|
|
1256
|
-
fi
|
|
1257
|
-
|
|
1258
|
-
json_ok "$result"
|
|
1259
|
-
;;
|
|
1260
|
-
flag-resolve)
|
|
1261
|
-
# Resolve a flag with optional resolution message
|
|
1262
|
-
# Usage: flag-resolve <flag_id> [resolution_message]
|
|
1263
|
-
flag_id="${1:-}"
|
|
1264
|
-
resolution="${2:-Resolved}"
|
|
1265
|
-
[[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-resolve <flag_id> [resolution_message]"
|
|
1266
|
-
|
|
1267
|
-
flags_file="$DATA_DIR/flags.json"
|
|
1268
|
-
[[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
|
|
1269
|
-
|
|
1270
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1271
|
-
|
|
1272
|
-
# Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
|
|
1273
|
-
if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
|
|
1274
|
-
json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
|
|
1275
|
-
else
|
|
1276
|
-
acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
|
|
1277
|
-
fi
|
|
1278
|
-
|
|
1279
|
-
updated=$(jq --arg id "$flag_id" --arg res "$resolution" --arg ts "$ts" '
|
|
1280
|
-
.flags = [.flags[] | if .id == $id then
|
|
1281
|
-
.resolved_at = $ts |
|
|
1282
|
-
.resolution = $res
|
|
1283
|
-
else . end]
|
|
1284
|
-
' "$flags_file") || {
|
|
1285
|
-
if type feature_enabled &>/dev/null && feature_enabled "file_locking"; then
|
|
1286
|
-
release_lock "$flags_file"
|
|
1287
|
-
fi
|
|
1288
|
-
json_err "$E_JSON_INVALID" "Failed to resolve flag"
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
atomic_write "$flags_file" "$updated"
|
|
1292
|
-
release_lock "$flags_file"
|
|
1293
|
-
json_ok "{\"resolved\":\"$flag_id\"}"
|
|
1294
|
-
;;
|
|
1295
|
-
flag-acknowledge)
|
|
1296
|
-
# Acknowledge a flag (issue continues but noted)
|
|
1297
|
-
# Usage: flag-acknowledge <flag_id>
|
|
1298
|
-
flag_id="${1:-}"
|
|
1299
|
-
[[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-acknowledge <flag_id>"
|
|
1300
|
-
|
|
1301
|
-
flags_file="$DATA_DIR/flags.json"
|
|
1302
|
-
[[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
|
|
1303
|
-
|
|
1304
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1305
|
-
|
|
1306
|
-
# Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
|
|
1307
|
-
if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
|
|
1308
|
-
json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
|
|
1309
|
-
else
|
|
1310
|
-
acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
|
|
1311
|
-
fi
|
|
1312
|
-
|
|
1313
|
-
updated=$(jq --arg id "$flag_id" --arg ts "$ts" '
|
|
1314
|
-
.flags = [.flags[] | if .id == $id then
|
|
1315
|
-
.acknowledged_at = $ts
|
|
1316
|
-
else . end]
|
|
1317
|
-
' "$flags_file") || {
|
|
1318
|
-
if type feature_enabled &>/dev/null && feature_enabled "file_locking"; then
|
|
1319
|
-
release_lock "$flags_file"
|
|
1320
|
-
fi
|
|
1321
|
-
json_err "$E_JSON_INVALID" "Failed to acknowledge flag"
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
atomic_write "$flags_file" "$updated"
|
|
1325
|
-
release_lock "$flags_file"
|
|
1326
|
-
json_ok "{\"acknowledged\":\"$flag_id\"}"
|
|
1327
|
-
;;
|
|
1328
|
-
flag-list)
|
|
1329
|
-
# List flags, optionally filtered
|
|
1330
|
-
# Usage: flag-list [--all] [--type blocker|issue|note] [--phase N]
|
|
1331
|
-
flags_file="$DATA_DIR/flags.json"
|
|
1332
|
-
show_all="false"
|
|
1333
|
-
filter_type=""
|
|
1334
|
-
filter_phase=""
|
|
1335
|
-
|
|
1336
|
-
while [[ $# -gt 0 ]]; do
|
|
1337
|
-
case "$1" in
|
|
1338
|
-
--all) show_all="true"; shift ;;
|
|
1339
|
-
--type) filter_type="$2"; shift 2 ;;
|
|
1340
|
-
--phase) filter_phase="$2"; shift 2 ;;
|
|
1341
|
-
*) shift ;;
|
|
1342
|
-
esac
|
|
1343
|
-
done
|
|
1344
|
-
|
|
1345
|
-
if [[ ! -f "$flags_file" ]]; then
|
|
1346
|
-
json_ok '{"flags":[],"count":0}'
|
|
1347
|
-
exit 0
|
|
1348
|
-
fi
|
|
1349
|
-
|
|
1350
|
-
# Build jq filter
|
|
1351
|
-
jq_filter='.flags'
|
|
1352
|
-
|
|
1353
|
-
if [[ "$show_all" != "true" ]]; then
|
|
1354
|
-
jq_filter+=' | [.[] | select(.resolved_at == null)]'
|
|
1355
|
-
fi
|
|
1356
|
-
|
|
1357
|
-
if [[ -n "$filter_type" ]]; then
|
|
1358
|
-
jq_filter+=" | [.[] | select(.type == \"$filter_type\")]"
|
|
1359
|
-
fi
|
|
1360
|
-
|
|
1361
|
-
if [[ -n "$filter_phase" && "$filter_phase" =~ ^[0-9]+$ ]]; then
|
|
1362
|
-
jq_filter+=" | [.[] | select(.phase == $filter_phase or .phase == null)]"
|
|
1363
|
-
fi
|
|
1364
|
-
|
|
1365
|
-
result=$(jq "{flags: ($jq_filter), count: ($jq_filter | length)}" "$flags_file")
|
|
1366
|
-
json_ok "$result"
|
|
1367
|
-
;;
|
|
1368
|
-
flag-auto-resolve)
|
|
1369
|
-
# Auto-resolve flags based on trigger (e.g., build_pass)
|
|
1370
|
-
# Usage: flag-auto-resolve <trigger>
|
|
1371
|
-
trigger="${1:-build_pass}"
|
|
1372
|
-
flags_file="$DATA_DIR/flags.json"
|
|
1373
|
-
|
|
1374
|
-
if [[ ! -f "$flags_file" ]]; then json_ok '{"resolved":0}'; exit 0; fi
|
|
1375
|
-
|
|
1376
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1377
|
-
|
|
1378
|
-
# Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
|
|
1379
|
-
lock_acquired=false
|
|
1380
|
-
if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
|
|
1381
|
-
json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
|
|
1382
|
-
else
|
|
1383
|
-
acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
|
|
1384
|
-
lock_acquired=true
|
|
1385
|
-
# Ensure lock is always released on exit (BUG-005/BUG-011 fix)
|
|
1386
|
-
trap 'release_lock "$flags_file" 2>/dev/null || true' EXIT
|
|
1387
|
-
fi
|
|
1388
|
-
|
|
1389
|
-
# Count how many will be resolved
|
|
1390
|
-
count=$(jq --arg trigger "$trigger" '
|
|
1391
|
-
[.flags[] | select(.auto_resolve_on == $trigger and .resolved_at == null)] | length
|
|
1392
|
-
' "$flags_file") || {
|
|
1393
|
-
json_err "$E_JSON_INVALID" "Failed to count flags for auto-resolve"
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
# Resolve them
|
|
1397
|
-
updated=$(jq --arg trigger "$trigger" --arg ts "$ts" '
|
|
1398
|
-
.flags = [.flags[] | if .auto_resolve_on == $trigger and .resolved_at == null then
|
|
1399
|
-
.resolved_at = $ts |
|
|
1400
|
-
.resolution = "Auto-resolved on " + $trigger
|
|
1401
|
-
else . end]
|
|
1402
|
-
' "$flags_file") || {
|
|
1403
|
-
json_err "$E_JSON_INVALID" "Failed to auto-resolve flags"
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
atomic_write "$flags_file" "$updated"
|
|
1407
|
-
# Lock released by trap on exit (BUG-005/BUG-011 fix)
|
|
1408
|
-
if [[ "$lock_acquired" == "true" ]]; then
|
|
1409
|
-
trap - EXIT
|
|
1410
|
-
fi
|
|
1411
|
-
json_ok "{\"resolved\":$count,\"trigger\":\"$trigger\"}"
|
|
1412
|
-
;;
|
|
1413
|
-
generate-ant-name)
|
|
1414
|
-
caste="${1:-builder}"
|
|
1415
|
-
# Caste-specific prefixes for personality
|
|
1416
|
-
case "$caste" in
|
|
1417
|
-
builder) prefixes=("Chip" "Hammer" "Forge" "Mason" "Brick" "Anvil" "Weld" "Bolt") ;;
|
|
1418
|
-
watcher) prefixes=("Vigil" "Sentinel" "Guard" "Keen" "Sharp" "Hawk" "Watch" "Alert") ;;
|
|
1419
|
-
scout) prefixes=("Swift" "Dash" "Ranger" "Track" "Seek" "Path" "Roam" "Quest") ;;
|
|
1420
|
-
colonizer) prefixes=("Pioneer" "Map" "Chart" "Venture" "Explore" "Compass" "Atlas" "Trek") ;;
|
|
1421
|
-
architect) prefixes=("Blueprint" "Draft" "Design" "Plan" "Schema" "Frame" "Sketch" "Model") ;;
|
|
1422
|
-
prime) prefixes=("Prime" "Alpha" "Lead" "Chief" "First" "Core" "Apex" "Crown") ;;
|
|
1423
|
-
chaos) prefixes=("Probe" "Stress" "Shake" "Twist" "Snap" "Breach" "Surge" "Jolt") ;;
|
|
1424
|
-
archaeologist) prefixes=("Relic" "Fossil" "Dig" "Shard" "Epoch" "Strata" "Lore" "Glyph") ;;
|
|
1425
|
-
oracle) prefixes=("Sage" "Seer" "Vision" "Augur" "Mystic" "Sibyl" "Delph" "Pythia") ;;
|
|
1426
|
-
ambassador) prefixes=("Bridge" "Connect" "Link" "Diplomat" "Protocol" "Network" "Port" "Socket") ;;
|
|
1427
|
-
auditor) prefixes=("Review" "Inspect" "Exam" "Scrutin" "Verify" "Check" "Audit" "Assess") ;;
|
|
1428
|
-
chronicler) prefixes=("Record" "Write" "Document" "Chronicle" "Scribe" "Archive" "Script" "Ledger") ;;
|
|
1429
|
-
gatekeeper) prefixes=("Guard" "Protect" "Secure" "Shield" "Defend" "Bar" "Gate" "Checkpoint") ;;
|
|
1430
|
-
guardian) prefixes=("Defend" "Patrol" "Watch" "Vigil" "Shield" "Guard" "Armor" "Fort") ;;
|
|
1431
|
-
includer) prefixes=("Access" "Include" "Open" "Welcome" "Reach" "Universal" "Equal" "A11y") ;;
|
|
1432
|
-
keeper) prefixes=("Archive" "Store" "Curate" "Preserve" "Guard" "Keep" "Hold" "Save") ;;
|
|
1433
|
-
measurer) prefixes=("Metric" "Gauge" "Scale" "Measure" "Benchmark" "Track" "Count" "Meter") ;;
|
|
1434
|
-
probe) prefixes=("Test" "Probe" "Excavat" "Uncover" "Edge" "Mutant" "Trial" "Check") ;;
|
|
1435
|
-
tracker) prefixes=("Track" "Trace" "Debug" "Hunt" "Follow" "Trail" "Find" "Seek") ;;
|
|
1436
|
-
weaver) prefixes=("Weave" "Knit" "Spin" "Twine" "Transform" "Mend" "Weave" "Weave") ;;
|
|
1437
|
-
*) prefixes=("Ant" "Worker" "Drone" "Toiler" "Marcher" "Runner" "Carrier" "Helper") ;;
|
|
1438
|
-
esac
|
|
1439
|
-
# Pick random prefix and add random number
|
|
1440
|
-
idx=$((RANDOM % ${#prefixes[@]}))
|
|
1441
|
-
prefix="${prefixes[$idx]}"
|
|
1442
|
-
num=$((RANDOM % 99 + 1))
|
|
1443
|
-
name="${prefix}-${num}"
|
|
1444
|
-
json_ok "\"$name\""
|
|
1445
|
-
;;
|
|
1446
|
-
|
|
1447
|
-
# ============================================
|
|
1448
|
-
# SWARM UTILITIES (ant:swarm support)
|
|
1449
|
-
# ============================================
|
|
1450
|
-
|
|
1451
|
-
autofix-checkpoint)
|
|
1452
|
-
# Create checkpoint before applying auto-fix
|
|
1453
|
-
# Usage: autofix-checkpoint [label]
|
|
1454
|
-
# Returns: {type: "stash"|"commit"|"none", ref: "..."}
|
|
1455
|
-
# IMPORTANT: Only stash Aether-related files, never touch user work
|
|
1456
|
-
if git rev-parse --git-dir >/dev/null 2>&1; then
|
|
1457
|
-
# Check if there are changes to Aether-managed files only
|
|
1458
|
-
# Target directories that Aether is allowed to modify
|
|
1459
|
-
target_dirs=".aether .claude/commands/ant .claude/commands/st .opencode runtime bin"
|
|
1460
|
-
has_changes=false
|
|
1461
|
-
|
|
1462
|
-
for dir in $target_dirs; do
|
|
1463
|
-
if [[ -d "$dir" ]] && [[ -n "$(git status --porcelain "$dir" 2>/dev/null)" ]]; then
|
|
1464
|
-
has_changes=true
|
|
1465
|
-
break
|
|
1466
|
-
fi
|
|
1467
|
-
done
|
|
1468
|
-
|
|
1469
|
-
if [[ "$has_changes" == "true" ]]; then
|
|
1470
|
-
label="${1:-autofix-$(date +%s)}"
|
|
1471
|
-
stash_name="aether-checkpoint: $label"
|
|
1472
|
-
# Only stash Aether-managed directories, never touch user files
|
|
1473
|
-
if git stash push -m "$stash_name" -- $target_dirs >/dev/null 2>&1; then
|
|
1474
|
-
json_ok "{\"type\":\"stash\",\"ref\":\"$stash_name\"}"
|
|
1475
|
-
else
|
|
1476
|
-
# Stash failed (possibly due to conflicts), record commit hash
|
|
1477
|
-
hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
1478
|
-
json_ok "{\"type\":\"commit\",\"ref\":\"$hash\"}"
|
|
1479
|
-
fi
|
|
1480
|
-
else
|
|
1481
|
-
# No changes in Aether-managed directories, just record commit hash
|
|
1482
|
-
hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
|
|
1483
|
-
json_ok "{\"type\":\"commit\",\"ref\":\"$hash\"}"
|
|
1484
|
-
fi
|
|
1485
|
-
else
|
|
1486
|
-
json_ok '{"type":"none","ref":null}'
|
|
1487
|
-
fi
|
|
1488
|
-
;;
|
|
1489
|
-
|
|
1490
|
-
autofix-rollback)
|
|
1491
|
-
# Rollback from checkpoint if fix failed
|
|
1492
|
-
# Usage: autofix-rollback <type> <ref>
|
|
1493
|
-
# Returns: {rolled_back: bool, method: "stash"|"reset"|"none"}
|
|
1494
|
-
ref_type="${1:-none}"
|
|
1495
|
-
ref="${2:-}"
|
|
1496
|
-
|
|
1497
|
-
case "$ref_type" in
|
|
1498
|
-
stash)
|
|
1499
|
-
# Find and pop the stash
|
|
1500
|
-
stash_ref=$(git stash list 2>/dev/null | grep "$ref" | head -1 | cut -d: -f1 || echo "")
|
|
1501
|
-
if [[ -n "$stash_ref" ]]; then
|
|
1502
|
-
if git stash pop "$stash_ref" >/dev/null 2>&1; then
|
|
1503
|
-
json_ok '{"rolled_back":true,"method":"stash"}'
|
|
1504
|
-
else
|
|
1505
|
-
json_ok '{"rolled_back":false,"method":"stash","error":"stash pop failed"}'
|
|
1506
|
-
fi
|
|
1507
|
-
else
|
|
1508
|
-
json_ok '{"rolled_back":false,"method":"stash","error":"stash not found"}'
|
|
1509
|
-
fi
|
|
1510
|
-
;;
|
|
1511
|
-
commit)
|
|
1512
|
-
# Reset to the commit
|
|
1513
|
-
if [[ -n "$ref" && "$ref" != "unknown" ]]; then
|
|
1514
|
-
if git reset --hard "$ref" >/dev/null 2>&1; then
|
|
1515
|
-
json_ok '{"rolled_back":true,"method":"reset"}'
|
|
1516
|
-
else
|
|
1517
|
-
json_ok '{"rolled_back":false,"method":"reset","error":"reset failed"}'
|
|
1518
|
-
fi
|
|
1519
|
-
else
|
|
1520
|
-
json_ok '{"rolled_back":false,"method":"reset","error":"invalid ref"}'
|
|
1521
|
-
fi
|
|
1522
|
-
;;
|
|
1523
|
-
none|*)
|
|
1524
|
-
json_ok '{"rolled_back":false,"method":"none"}'
|
|
1525
|
-
;;
|
|
1526
|
-
esac
|
|
1527
|
-
;;
|
|
1528
|
-
|
|
1529
|
-
spawn-can-spawn-swarm)
|
|
1530
|
-
# Check if swarm can spawn more scouts (separate from phase workers)
|
|
1531
|
-
# Usage: spawn-can-spawn-swarm <swarm_id>
|
|
1532
|
-
# Swarm has its own cap of 6 (4 scouts + 2 sub-scouts max)
|
|
1533
|
-
swarm_id="${1:-swarm}"
|
|
1534
|
-
swarm_cap=6
|
|
1535
|
-
|
|
1536
|
-
current=0
|
|
1537
|
-
if [[ -f "$DATA_DIR/spawn-tree.txt" ]]; then
|
|
1538
|
-
current=$(grep -c "|swarm:$swarm_id$" "$DATA_DIR/spawn-tree.txt" 2>/dev/null || echo 0)
|
|
1539
|
-
fi
|
|
1540
|
-
|
|
1541
|
-
if [[ $current -lt $swarm_cap ]]; then
|
|
1542
|
-
can="true"
|
|
1543
|
-
remaining=$((swarm_cap - current))
|
|
1544
|
-
else
|
|
1545
|
-
can="false"
|
|
1546
|
-
remaining=0
|
|
1547
|
-
fi
|
|
1548
|
-
|
|
1549
|
-
json_ok "{\"can_spawn\":$can,\"current\":$current,\"cap\":$swarm_cap,\"remaining\":$remaining,\"swarm_id\":\"$swarm_id\"}"
|
|
1550
|
-
;;
|
|
1551
|
-
|
|
1552
|
-
swarm-findings-init)
|
|
1553
|
-
# Initialize swarm findings file
|
|
1554
|
-
# Usage: swarm-findings-init <swarm_id>
|
|
1555
|
-
swarm_id="${1:-swarm-$(date +%s)}"
|
|
1556
|
-
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1557
|
-
|
|
1558
|
-
mkdir -p "$DATA_DIR"
|
|
1559
|
-
cat > "$findings_file" <<EOF
|
|
1560
|
-
{
|
|
1561
|
-
"swarm_id": "$swarm_id",
|
|
1562
|
-
"created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
1563
|
-
"status": "active",
|
|
1564
|
-
"findings": [],
|
|
1565
|
-
"solution": null
|
|
1566
|
-
}
|
|
1567
|
-
EOF
|
|
1568
|
-
json_ok "{\"swarm_id\":\"$swarm_id\",\"file\":\"$findings_file\"}"
|
|
1569
|
-
;;
|
|
1570
|
-
|
|
1571
|
-
swarm-findings-add)
|
|
1572
|
-
# Add a finding from a scout
|
|
1573
|
-
# Usage: swarm-findings-add <swarm_id> <scout_type> <confidence> <finding_json>
|
|
1574
|
-
swarm_id="${1:-}"
|
|
1575
|
-
scout_type="${2:-}"
|
|
1576
|
-
confidence="${3:-0.5}"
|
|
1577
|
-
finding="${4:-}"
|
|
1578
|
-
|
|
1579
|
-
[[ -z "$swarm_id" || -z "$scout_type" || -z "$finding" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-add <swarm_id> <scout_type> <confidence> <finding_json>"
|
|
1580
|
-
|
|
1581
|
-
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1582
|
-
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
1583
|
-
|
|
1584
|
-
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1585
|
-
|
|
1586
|
-
# Add finding to array
|
|
1587
|
-
updated=$(jq --arg scout "$scout_type" --arg conf "$confidence" --arg ts "$ts" --argjson finding "$finding" '
|
|
1588
|
-
.findings += [{
|
|
1589
|
-
"scout": $scout,
|
|
1590
|
-
"confidence": ($conf | tonumber),
|
|
1591
|
-
"timestamp": $ts,
|
|
1592
|
-
"finding": $finding
|
|
1593
|
-
}]
|
|
1594
|
-
' "$findings_file")
|
|
1595
|
-
|
|
1596
|
-
echo "$updated" > "$findings_file"
|
|
1597
|
-
count=$(echo "$updated" | jq '.findings | length')
|
|
1598
|
-
json_ok "{\"added\":true,\"scout\":\"$scout_type\",\"total_findings\":$count}"
|
|
1599
|
-
;;
|
|
1600
|
-
|
|
1601
|
-
swarm-findings-read)
|
|
1602
|
-
# Read all findings for a swarm
|
|
1603
|
-
# Usage: swarm-findings-read <swarm_id>
|
|
1604
|
-
swarm_id="${1:-}"
|
|
1605
|
-
[[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-read <swarm_id>"
|
|
1606
|
-
|
|
1607
|
-
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1608
|
-
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
1609
|
-
|
|
1610
|
-
json_ok "$(cat "$findings_file")"
|
|
1611
|
-
;;
|
|
1612
|
-
|
|
1613
|
-
swarm-solution-set)
|
|
1614
|
-
# Set the chosen solution for a swarm
|
|
1615
|
-
# Usage: swarm-solution-set <swarm_id> <solution_json>
|
|
1616
|
-
swarm_id="${1:-}"
|
|
1617
|
-
solution="${2:-}"
|
|
1618
|
-
|
|
1619
|
-
[[ -z "$swarm_id" || -z "$solution" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-solution-set <swarm_id> <solution_json>"
|
|
1620
|
-
|
|
1621
|
-
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1622
|
-
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
1623
|
-
|
|
1624
|
-
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1625
|
-
|
|
1626
|
-
updated=$(jq --argjson solution "$solution" --arg ts "$ts" '
|
|
1627
|
-
.solution = $solution |
|
|
1628
|
-
.status = "resolved" |
|
|
1629
|
-
.resolved_at = $ts
|
|
1630
|
-
' "$findings_file")
|
|
1631
|
-
|
|
1632
|
-
echo "$updated" > "$findings_file"
|
|
1633
|
-
json_ok "{\"solution_set\":true,\"swarm_id\":\"$swarm_id\"}"
|
|
1634
|
-
;;
|
|
1635
|
-
|
|
1636
|
-
swarm-cleanup)
|
|
1637
|
-
# Clean up swarm files after completion
|
|
1638
|
-
# Usage: swarm-cleanup <swarm_id> [--archive]
|
|
1639
|
-
swarm_id="${1:-}"
|
|
1640
|
-
archive="${2:-}"
|
|
1641
|
-
|
|
1642
|
-
[[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-cleanup <swarm_id> [--archive]"
|
|
1643
|
-
|
|
1644
|
-
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1645
|
-
|
|
1646
|
-
if [[ -f "$findings_file" ]]; then
|
|
1647
|
-
if [[ "$archive" == "--archive" ]]; then
|
|
1648
|
-
mkdir -p "$DATA_DIR/swarm-archive"
|
|
1649
|
-
mv "$findings_file" "$DATA_DIR/swarm-archive/"
|
|
1650
|
-
json_ok "{\"archived\":true,\"swarm_id\":\"$swarm_id\"}"
|
|
1651
|
-
else
|
|
1652
|
-
rm -f "$findings_file"
|
|
1653
|
-
json_ok "{\"deleted\":true,\"swarm_id\":\"$swarm_id\"}"
|
|
1654
|
-
fi
|
|
1655
|
-
else
|
|
1656
|
-
json_ok "{\"not_found\":true,\"swarm_id\":\"$swarm_id\"}"
|
|
1657
|
-
fi
|
|
1658
|
-
;;
|
|
1659
|
-
|
|
1660
|
-
grave-add)
|
|
1661
|
-
# Record a grave marker when a builder fails at a file
|
|
1662
|
-
# Usage: grave-add <file> <ant_name> <task_id> <phase> <failure_summary> [function] [line]
|
|
1663
|
-
[[ $# -ge 5 ]] || json_err "$E_VALIDATION_FAILED" "Usage: grave-add <file> <ant_name> <task_id> <phase> <failure_summary> [function] [line]"
|
|
1664
|
-
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
1665
|
-
file="$1"
|
|
1666
|
-
ant_name="$2"
|
|
1667
|
-
task_id="$3"
|
|
1668
|
-
phase="$4"
|
|
1669
|
-
failure_summary="$5"
|
|
1670
|
-
func="${6:-null}"
|
|
1671
|
-
line="${7:-null}"
|
|
1672
|
-
id="grave_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
|
|
1673
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1674
|
-
if [[ "$phase" =~ ^[0-9]+$ ]]; then
|
|
1675
|
-
phase_jq="$phase"
|
|
1676
|
-
else
|
|
1677
|
-
phase_jq="null"
|
|
1678
|
-
fi
|
|
1679
|
-
if [[ "$func" == "null" ]]; then
|
|
1680
|
-
func_jq="null"
|
|
1681
|
-
else
|
|
1682
|
-
func_jq="\"$func\""
|
|
1683
|
-
fi
|
|
1684
|
-
if [[ "$line" =~ ^[0-9]+$ ]]; then
|
|
1685
|
-
line_jq="$line"
|
|
1686
|
-
else
|
|
1687
|
-
line_jq="null"
|
|
1688
|
-
fi
|
|
1689
|
-
updated=$(jq --arg id "$id" --arg file "$file" --arg ant "$ant_name" --arg tid "$task_id" \
|
|
1690
|
-
--argjson phase "$phase_jq" --arg summary "$failure_summary" \
|
|
1691
|
-
--argjson func "$func_jq" --argjson line "$line_jq" --arg ts "$ts" '
|
|
1692
|
-
(.graveyards // []) as $graves |
|
|
1693
|
-
. + {graveyards: ($graves + [{
|
|
1694
|
-
id: $id,
|
|
1695
|
-
file: $file,
|
|
1696
|
-
ant_name: $ant,
|
|
1697
|
-
task_id: $tid,
|
|
1698
|
-
phase: $phase,
|
|
1699
|
-
failure_summary: $summary,
|
|
1700
|
-
function: $func,
|
|
1701
|
-
line: $line,
|
|
1702
|
-
timestamp: $ts
|
|
1703
|
-
}])} |
|
|
1704
|
-
if (.graveyards | length) > 30 then .graveyards = .graveyards[-30:] else . end
|
|
1705
|
-
' "$DATA_DIR/COLONY_STATE.json") || json_err "$E_JSON_INVALID" "Failed to update COLONY_STATE.json"
|
|
1706
|
-
atomic_write "$DATA_DIR/COLONY_STATE.json" "$updated"
|
|
1707
|
-
json_ok "\"$id\""
|
|
1708
|
-
;;
|
|
1709
|
-
|
|
1710
|
-
grave-check)
|
|
1711
|
-
# Query for grave markers near a file path
|
|
1712
|
-
# Usage: grave-check <file_path>
|
|
1713
|
-
# Read-only, never modifies state
|
|
1714
|
-
[[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: grave-check <file_path>"
|
|
1715
|
-
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
1716
|
-
check_file="$1"
|
|
1717
|
-
check_dir=$(dirname "$check_file")
|
|
1718
|
-
json_ok "$(jq --arg file "$check_file" --arg dir "$check_dir" '
|
|
1719
|
-
(.graveyards // []) as $graves |
|
|
1720
|
-
($graves | map(select(.file == $file))) as $exact |
|
|
1721
|
-
($graves | map(select((.file | split("/")[:-1] | join("/")) == $dir))) as $dir_matches |
|
|
1722
|
-
($exact | length) as $exact_count |
|
|
1723
|
-
($dir_matches | length) as $dir_count |
|
|
1724
|
-
(if $exact_count > 0 then "high"
|
|
1725
|
-
elif $dir_count >= 2 then "high"
|
|
1726
|
-
elif $dir_count == 1 then "low"
|
|
1727
|
-
else "none" end) as $caution |
|
|
1728
|
-
{graves: $dir_matches, count: $dir_count, exact_matches: $exact_count, caution_level: $caution}
|
|
1729
|
-
' "$DATA_DIR/COLONY_STATE.json")"
|
|
1730
|
-
;;
|
|
1731
|
-
|
|
1732
|
-
# ============================================
|
|
1733
|
-
# GIT COMMIT UTILITIES
|
|
1734
|
-
# ============================================
|
|
1735
|
-
|
|
1736
|
-
generate-commit-message)
|
|
1737
|
-
# Generate an intelligent commit message from colony context
|
|
1738
|
-
# Usage: generate-commit-message <type> <phase_id> <phase_name> [summary|ai_description] [plan_num]
|
|
1739
|
-
# Types: "milestone" | "pause" | "fix" | "contextual"
|
|
1740
|
-
# Returns: {"message": "...", "body": "...", "files_changed": N, ...}
|
|
1741
|
-
|
|
1742
|
-
msg_type="${1:-milestone}"
|
|
1743
|
-
phase_id="${2:-0}"
|
|
1744
|
-
phase_name="${3:-unknown}"
|
|
1745
|
-
summary="${4:-}" # For milestone/fix types, or ai_description for contextual type
|
|
1746
|
-
plan_num="${5:-01}" # Optional: plan number for contextual type (e.g., "01")
|
|
1747
|
-
|
|
1748
|
-
# Count changed files
|
|
1749
|
-
files_changed=0
|
|
1750
|
-
if git rev-parse --git-dir >/dev/null 2>&1; then
|
|
1751
|
-
files_changed=$(git diff --stat --cached HEAD 2>/dev/null | tail -1 | grep -oE '[0-9]+ file' | grep -oE '[0-9]+' || echo "0")
|
|
1752
|
-
if [[ "$files_changed" == "0" ]]; then
|
|
1753
|
-
files_changed=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
1754
|
-
fi
|
|
1755
|
-
fi
|
|
1756
|
-
|
|
1757
|
-
case "$msg_type" in
|
|
1758
|
-
milestone)
|
|
1759
|
-
# Format: aether-milestone: phase N complete -- <name>
|
|
1760
|
-
if [[ -n "$summary" ]]; then
|
|
1761
|
-
message="aether-milestone: phase ${phase_id} complete -- ${summary}"
|
|
1762
|
-
else
|
|
1763
|
-
message="aether-milestone: phase ${phase_id} complete -- ${phase_name}"
|
|
1764
|
-
fi
|
|
1765
|
-
body="All verification gates passed. User confirmed runtime behavior."
|
|
1766
|
-
;;
|
|
1767
|
-
pause)
|
|
1768
|
-
message="aether-checkpoint: session pause -- phase ${phase_id} in progress"
|
|
1769
|
-
body="Colony paused mid-session. Handoff document saved."
|
|
1770
|
-
;;
|
|
1771
|
-
fix)
|
|
1772
|
-
if [[ -n "$summary" ]]; then
|
|
1773
|
-
message="fix: ${summary}"
|
|
1774
|
-
else
|
|
1775
|
-
message="fix: resolve issue in phase ${phase_id}"
|
|
1776
|
-
fi
|
|
1777
|
-
body="Swarm-verified fix applied and tested."
|
|
1778
|
-
;;
|
|
1779
|
-
contextual)
|
|
1780
|
-
# NEW: Contextual commit with AI description and structured metadata
|
|
1781
|
-
# Derive subsystem from phase name (e.g., "11-foraging-specialization" -> "foraging")
|
|
1782
|
-
subsystem=$(echo "$phase_name" | sed -E 's/^[0-9]+-//' | sed -E 's/-[0-9]+.*$//' | tr '-' ' ')
|
|
1783
|
-
[[ -z "$subsystem" ]] && subsystem="phase"
|
|
1784
|
-
|
|
1785
|
-
# Build message with AI description (summary parameter is reused as ai_description)
|
|
1786
|
-
if [[ -n "$summary" ]]; then
|
|
1787
|
-
message="aether-milestone: ${summary}"
|
|
1788
|
-
else
|
|
1789
|
-
# Fallback if no AI description provided
|
|
1790
|
-
message="aether-milestone: phase ${phase_id}.${plan_num} complete -- ${phase_name}"
|
|
1791
|
-
fi
|
|
1792
|
-
|
|
1793
|
-
# Build structured body with metadata
|
|
1794
|
-
body="Scope: ${phase_id}.${plan_num}
|
|
1795
|
-
Files: ${files_changed} files changed"
|
|
1796
|
-
|
|
1797
|
-
# Truncate message if needed BEFORE JSON construction
|
|
1798
|
-
if [[ ${#message} -gt 72 ]]; then
|
|
1799
|
-
message="${message:0:69}..."
|
|
1800
|
-
fi
|
|
1801
|
-
|
|
1802
|
-
# Return enhanced JSON with additional metadata
|
|
1803
|
-
json_ok "{\"message\":\"$message\",\"body\":\"$body\",\"files_changed\":$files_changed,\"subsystem\":\"$subsystem\",\"scope\":\"${phase_id}.${plan_num}\"}"
|
|
1804
|
-
exit 0
|
|
1805
|
-
;;
|
|
1806
|
-
*)
|
|
1807
|
-
message="aether-checkpoint: phase ${phase_id}"
|
|
1808
|
-
body=""
|
|
1809
|
-
;;
|
|
1810
|
-
esac
|
|
1811
|
-
|
|
1812
|
-
# Enforce 72-char limit on subject line (truncate if needed)
|
|
1813
|
-
if [[ ${#message} -gt 72 ]]; then
|
|
1814
|
-
message="${message:0:69}..."
|
|
1815
|
-
fi
|
|
1816
|
-
|
|
1817
|
-
json_ok "{\"message\":\"$message\",\"body\":\"$body\",\"files_changed\":$files_changed}"
|
|
1818
|
-
;;
|
|
1819
|
-
|
|
1820
|
-
# ============================================
|
|
1821
|
-
# CONTEXT PERSISTENCE SYSTEM
|
|
1822
|
-
# ============================================
|
|
1823
|
-
|
|
1824
|
-
context-update)
|
|
1825
|
-
# Update .aether/CONTEXT.md with current colony state
|
|
1826
|
-
# Usage: context-update <action> [args...]
|
|
1827
|
-
#
|
|
1828
|
-
# Actions:
|
|
1829
|
-
# init <goal> - Initialize new context
|
|
1830
|
-
# update-phase <phase_id> <name> - Update current phase
|
|
1831
|
-
# activity <command> <result> [files] - Log activity
|
|
1832
|
-
# constraint <type> <message> [source] - Add constraint (redirect/focus)
|
|
1833
|
-
# decision <description> [rationale] [who] - Log decision
|
|
1834
|
-
# safe-to-clear <yes|no> <reason> - Set safe-to-clear status
|
|
1835
|
-
# build-start <phase_id> <workers> <tasks> - Mark build starting
|
|
1836
|
-
# worker-spawn <ant_name> <caste> <task> - Log worker spawn
|
|
1837
|
-
# worker-complete <ant_name> <status> - Log worker completion
|
|
1838
|
-
# build-progress <completed> <total> - Update build progress
|
|
1839
|
-
# build-complete <status> <result> - Mark build complete
|
|
1840
|
-
#
|
|
1841
|
-
# Always call with explicit arguments - never rely on current directory
|
|
1842
|
-
# CONTEXT_FILE must be passed or detected from AETHER_ROOT
|
|
1843
|
-
_cmd_context_update "$@"
|
|
1844
|
-
;;
|
|
1845
|
-
|
|
1846
|
-
# ============================================
|
|
1847
|
-
# REGISTRY & UPDATE UTILITIES
|
|
1848
|
-
# ============================================
|
|
1849
|
-
|
|
1850
|
-
version-check)
|
|
1851
|
-
# Compare local .aether/version.json vs ~/.aether/version.json
|
|
1852
|
-
# Outputs a notice string if versions differ, empty if matched or missing
|
|
1853
|
-
local_version_file="$AETHER_ROOT/.aether/version.json"
|
|
1854
|
-
hub_version_file="$HOME/.aether/version.json"
|
|
1855
|
-
|
|
1856
|
-
# Silent exit if either file is missing
|
|
1857
|
-
if [[ ! -f "$local_version_file" || ! -f "$hub_version_file" ]]; then
|
|
1858
|
-
json_ok '""'
|
|
1859
|
-
exit 0
|
|
1860
|
-
fi
|
|
1861
|
-
|
|
1862
|
-
local_ver=$(jq -r '.version // "unknown"' "$local_version_file" 2>/dev/null || echo "unknown")
|
|
1863
|
-
hub_ver=$(jq -r '.version // "unknown"' "$hub_version_file" 2>/dev/null || echo "unknown")
|
|
1864
|
-
|
|
1865
|
-
if [[ "$local_ver" == "$hub_ver" ]]; then
|
|
1866
|
-
json_ok '""'
|
|
1867
|
-
else
|
|
1868
|
-
printf -v msg 'Update available: %s to %s (run /ant:update)' "$local_ver" "$hub_ver"
|
|
1869
|
-
json_ok "$msg"
|
|
1870
|
-
fi
|
|
1871
|
-
;;
|
|
1872
|
-
|
|
1873
|
-
registry-add)
|
|
1874
|
-
# Add or update a repo entry in ~/.aether/registry.json
|
|
1875
|
-
# Usage: registry-add <repo_path> <version>
|
|
1876
|
-
repo_path="${1:-}"
|
|
1877
|
-
repo_version="${2:-}"
|
|
1878
|
-
[[ -z "$repo_path" || -z "$repo_version" ]] && json_err "$E_VALIDATION_FAILED" "Usage: registry-add <repo_path> <version>"
|
|
1879
|
-
|
|
1880
|
-
registry_file="$HOME/.aether/registry.json"
|
|
1881
|
-
mkdir -p "$HOME/.aether"
|
|
1882
|
-
|
|
1883
|
-
if [[ ! -f "$registry_file" ]]; then
|
|
1884
|
-
echo '{"schema_version":1,"repos":[]}' > "$registry_file"
|
|
1885
|
-
fi
|
|
1886
|
-
|
|
1887
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
1888
|
-
|
|
1889
|
-
# Check if repo already exists in registry
|
|
1890
|
-
existing=$(jq --arg path "$repo_path" '.repos[] | select(.path == $path)' "$registry_file" 2>/dev/null)
|
|
1891
|
-
|
|
1892
|
-
if [[ -n "$existing" ]]; then
|
|
1893
|
-
# Update existing entry
|
|
1894
|
-
updated=$(jq --arg path "$repo_path" --arg ver "$repo_version" --arg ts "$ts" '
|
|
1895
|
-
.repos = [.repos[] | if .path == $path then
|
|
1896
|
-
.version = $ver |
|
|
1897
|
-
.updated_at = $ts
|
|
1898
|
-
else . end]
|
|
1899
|
-
' "$registry_file") || json_err "$E_JSON_INVALID" "Failed to update registry"
|
|
1900
|
-
else
|
|
1901
|
-
# Add new entry
|
|
1902
|
-
updated=$(jq --arg path "$repo_path" --arg ver "$repo_version" --arg ts "$ts" '
|
|
1903
|
-
.repos += [{
|
|
1904
|
-
"path": $path,
|
|
1905
|
-
"version": $ver,
|
|
1906
|
-
"registered_at": $ts,
|
|
1907
|
-
"updated_at": $ts
|
|
1908
|
-
}]
|
|
1909
|
-
' "$registry_file") || json_err "$E_JSON_INVALID" "Failed to update registry"
|
|
1910
|
-
fi
|
|
1911
|
-
|
|
1912
|
-
echo "$updated" > "$registry_file"
|
|
1913
|
-
json_ok "{\"registered\":true,\"path\":\"$repo_path\",\"version\":\"$repo_version\"}"
|
|
1914
|
-
;;
|
|
1915
|
-
|
|
1916
|
-
bootstrap-system)
|
|
1917
|
-
# Copy system files from ~/.aether/system/ into local .aether/
|
|
1918
|
-
# Uses explicit allowlist — never touches colony data
|
|
1919
|
-
hub_system="$HOME/.aether/system"
|
|
1920
|
-
local_aether="$AETHER_ROOT/.aether"
|
|
1921
|
-
|
|
1922
|
-
[[ ! -d "$hub_system" ]] && json_err "$E_HUB_NOT_FOUND" "Hub system directory not found: $hub_system"
|
|
1923
|
-
|
|
1924
|
-
# Allowlist of system files to copy (relative to system/)
|
|
1925
|
-
allowlist=(
|
|
1926
|
-
"aether-utils.sh"
|
|
1927
|
-
"coding-standards.md"
|
|
1928
|
-
"debugging.md"
|
|
1929
|
-
"DISCIPLINES.md"
|
|
1930
|
-
"learning.md"
|
|
1931
|
-
"planning.md"
|
|
1932
|
-
"QUEEN_ANT_ARCHITECTURE.md"
|
|
1933
|
-
"tdd.md"
|
|
1934
|
-
"verification-loop.md"
|
|
1935
|
-
"verification.md"
|
|
1936
|
-
"workers.md"
|
|
1937
|
-
"docs/constraints.md"
|
|
1938
|
-
"docs/pathogen-schema-example.json"
|
|
1939
|
-
"docs/pathogen-schema.md"
|
|
1940
|
-
"docs/pheromones.md"
|
|
1941
|
-
"docs/progressive-disclosure.md"
|
|
1942
|
-
"utils/atomic-write.sh"
|
|
1943
|
-
"utils/colorize-log.sh"
|
|
1944
|
-
"utils/file-lock.sh"
|
|
1945
|
-
"utils/watch-spawn-tree.sh"
|
|
1946
|
-
)
|
|
1947
|
-
|
|
1948
|
-
copied=0
|
|
1949
|
-
for file in "${allowlist[@]}"; do
|
|
1950
|
-
src="$hub_system/$file"
|
|
1951
|
-
dest="$local_aether/$file"
|
|
1952
|
-
if [[ -f "$src" ]]; then
|
|
1953
|
-
mkdir -p "$(dirname "$dest")"
|
|
1954
|
-
cp "$src" "$dest"
|
|
1955
|
-
# Preserve executable bit for shell scripts
|
|
1956
|
-
if [[ "$file" == *.sh ]]; then
|
|
1957
|
-
chmod 755 "$dest"
|
|
1958
|
-
fi
|
|
1959
|
-
copied=$((copied + 1))
|
|
1960
|
-
fi
|
|
1961
|
-
done
|
|
1962
|
-
|
|
1963
|
-
json_ok "{\"copied\":$copied,\"total\":${#allowlist[@]}}"
|
|
1964
|
-
;;
|
|
1965
|
-
|
|
1966
|
-
load-state)
|
|
1967
|
-
source "$SCRIPT_DIR/utils/state-loader.sh" 2>/dev/null || {
|
|
1968
|
-
json_err "$E_FILE_NOT_FOUND" "state-loader.sh not found"
|
|
1969
|
-
exit 1
|
|
1970
|
-
}
|
|
1971
|
-
load_colony_state
|
|
1972
|
-
if [[ $? -eq 0 ]]; then
|
|
1973
|
-
# Output success with handoff info if detected
|
|
1974
|
-
if [[ "$HANDOFF_DETECTED" == "true" ]]; then
|
|
1975
|
-
json_ok "{\"loaded\":true,\"handoff_detected\":true,\"handoff_summary\":\"$(get_handoff_summary)\"}"
|
|
1976
|
-
else
|
|
1977
|
-
json_ok '{"loaded":true}'
|
|
1978
|
-
fi
|
|
1979
|
-
fi
|
|
1980
|
-
# Note: load_colony_state handles its own error output
|
|
1981
|
-
;;
|
|
1982
|
-
|
|
1983
|
-
unload-state)
|
|
1984
|
-
source "$SCRIPT_DIR/utils/state-loader.sh" 2>/dev/null || {
|
|
1985
|
-
json_err "$E_FILE_NOT_FOUND" "state-loader.sh not found"
|
|
1986
|
-
exit 1
|
|
1987
|
-
}
|
|
1988
|
-
unload_colony_state
|
|
1989
|
-
json_ok '{"unloaded":true}'
|
|
1990
|
-
;;
|
|
1991
|
-
|
|
1992
|
-
spawn-tree-load)
|
|
1993
|
-
source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || {
|
|
1994
|
-
json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
|
|
1995
|
-
exit 1
|
|
1996
|
-
}
|
|
1997
|
-
tree_json=$(reconstruct_tree_json)
|
|
1998
|
-
json_ok "$tree_json"
|
|
1999
|
-
;;
|
|
2000
|
-
|
|
2001
|
-
spawn-tree-active)
|
|
2002
|
-
source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || {
|
|
2003
|
-
json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
|
|
2004
|
-
exit 1
|
|
2005
|
-
}
|
|
2006
|
-
active=$(get_active_spawns)
|
|
2007
|
-
json_ok "$active"
|
|
2008
|
-
;;
|
|
2009
|
-
|
|
2010
|
-
spawn-tree-depth)
|
|
2011
|
-
ant_name="${1:-}"
|
|
2012
|
-
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-tree-depth <ant_name>"
|
|
2013
|
-
source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || {
|
|
2014
|
-
json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
|
|
2015
|
-
exit 1
|
|
2016
|
-
}
|
|
2017
|
-
depth=$(get_spawn_depth "$ant_name")
|
|
2018
|
-
json_ok "$depth"
|
|
2019
|
-
;;
|
|
2020
|
-
|
|
2021
|
-
# --- Model Profile Commands ---
|
|
2022
|
-
model-profile)
|
|
2023
|
-
action="${1:-get}"
|
|
2024
|
-
case "$action" in
|
|
2025
|
-
get)
|
|
2026
|
-
caste="${2:-}"
|
|
2027
|
-
[[ -z "$caste" ]] && json_err "$E_VALIDATION_FAILED" "Usage: model-profile get <caste>"
|
|
2028
|
-
|
|
2029
|
-
profile_file="$AETHER_ROOT/.aether/model-profiles.yaml"
|
|
2030
|
-
if [[ ! -f "$profile_file" ]]; then
|
|
2031
|
-
json_ok '{"model":"kimi-k2.5","source":"default","caste":"'$caste'"}'
|
|
2032
|
-
exit 0
|
|
2033
|
-
fi
|
|
2034
|
-
|
|
2035
|
-
# Extract model for caste using awk (bash-compatible YAML parsing)
|
|
2036
|
-
model=$(awk '/^worker_models:/{found=1; next} found && /^[^ ]/{exit} found && /^ '$caste':/{print $2; exit}' "$profile_file" 2>/dev/null)
|
|
2037
|
-
|
|
2038
|
-
[[ -z "$model" ]] && model="kimi-k2.5"
|
|
2039
|
-
json_ok '{"model":"'$model'","source":"profile","caste":"'$caste'"}'
|
|
2040
|
-
;;
|
|
2041
|
-
|
|
2042
|
-
list)
|
|
2043
|
-
profile_file="$AETHER_ROOT/.aether/model-profiles.yaml"
|
|
2044
|
-
if [[ ! -f "$profile_file" ]]; then
|
|
2045
|
-
json_ok '{"models":{},"source":"default"}'
|
|
2046
|
-
exit 0
|
|
2047
|
-
fi
|
|
2048
|
-
|
|
2049
|
-
# Extract all caste:model pairs as JSON
|
|
2050
|
-
# Lines look like: " prime: glm-5 # Complex coordination..."
|
|
2051
|
-
models=$(awk '/^worker_models:/{found=1; next} found && /^[^ ]/{exit} found && /^ [a-z_]+:/{gsub(/:/,""); printf "\"%s\":\"%s\",", $1, $2}' "$profile_file" 2>/dev/null)
|
|
2052
|
-
# Remove trailing comma
|
|
2053
|
-
models="${models%,}"
|
|
2054
|
-
|
|
2055
|
-
json_ok '{"models":{'$models'},"source":"profile"}'
|
|
2056
|
-
;;
|
|
2057
|
-
|
|
2058
|
-
verify)
|
|
2059
|
-
profile_file="$AETHER_ROOT/.aether/model-profiles.yaml"
|
|
2060
|
-
[[ ! -f "$profile_file" ]] && json_err "$E_FILE_NOT_FOUND" "Profile not found" '{"file":"model-profiles.yaml"}'
|
|
2061
|
-
|
|
2062
|
-
# Check proxy health
|
|
2063
|
-
proxy_health=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/health 2>/dev/null || echo "000")
|
|
2064
|
-
proxy_status=$([[ "$proxy_health" == "200" ]] && echo "healthy" || echo "unhealthy")
|
|
2065
|
-
|
|
2066
|
-
# Count castes
|
|
2067
|
-
caste_count=$(awk '/^worker_models:/{found=1; next} found && /^[^ ]/{exit} found && /^ [a-z_]+:/{count++} END{print count+0}' "$profile_file" 2>/dev/null)
|
|
2068
|
-
|
|
2069
|
-
json_ok '{"profile_exists":true,"caste_count":'$caste_count',"proxy_status":"'$proxy_status'","proxy_endpoint":"http://localhost:4000"}'
|
|
2070
|
-
;;
|
|
2071
|
-
|
|
2072
|
-
select)
|
|
2073
|
-
# Usage: model-profile select <caste> <task_description> [cli_override]
|
|
2074
|
-
# Returns: JSON with model and source
|
|
2075
|
-
caste="$2"
|
|
2076
|
-
task_description="$3"
|
|
2077
|
-
cli_override="${4:-}"
|
|
2078
|
-
|
|
2079
|
-
[[ -z "$caste" ]] && json_err "$E_VALIDATION_FAILED" "Usage: model-profile select <caste> <task_description> [cli_override]"
|
|
2080
|
-
|
|
2081
|
-
# Create a temporary Node.js script to call the library
|
|
2082
|
-
node_script=$(cat << 'NODESCRIPT'
|
|
2083
|
-
const { loadModelProfiles, selectModelForTask } = require('./bin/lib/model-profiles');
|
|
2084
|
-
const caste = process.argv[2];
|
|
2085
|
-
const taskDescription = process.argv[3];
|
|
2086
|
-
const cliOverride = process.argv[4] || null;
|
|
2087
|
-
|
|
2088
|
-
try {
|
|
2089
|
-
const profiles = loadModelProfiles('.');
|
|
2090
|
-
const result = selectModelForTask(profiles, caste, taskDescription, cliOverride);
|
|
2091
|
-
console.log(JSON.stringify({ ok: true, result }));
|
|
2092
|
-
} catch (error) {
|
|
2093
|
-
console.log(JSON.stringify({ ok: false, error: error.message }));
|
|
2094
|
-
process.exit(1);
|
|
2095
|
-
}
|
|
2096
|
-
NODESCRIPT
|
|
2097
|
-
)
|
|
2098
|
-
|
|
2099
|
-
result=$(echo "$node_script" | node - "$caste" "$task_description" "$cli_override")
|
|
2100
|
-
echo "$result"
|
|
2101
|
-
;;
|
|
2102
|
-
|
|
2103
|
-
validate)
|
|
2104
|
-
# Usage: model-profile validate <model_name>
|
|
2105
|
-
# Returns: JSON with valid boolean
|
|
2106
|
-
model_name="$2"
|
|
2107
|
-
|
|
2108
|
-
[[ -z "$model_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: model-profile validate <model_name>"
|
|
2109
|
-
|
|
2110
|
-
node_script=$(cat << 'NODESCRIPT'
|
|
2111
|
-
const { loadModelProfiles, validateModel } = require('./bin/lib/model-profiles');
|
|
2112
|
-
const modelName = process.argv[2];
|
|
2113
|
-
|
|
2114
|
-
try {
|
|
2115
|
-
const profiles = loadModelProfiles('.');
|
|
2116
|
-
const validation = validateModel(profiles, modelName);
|
|
2117
|
-
console.log(JSON.stringify({ ok: true, result: validation }));
|
|
2118
|
-
} catch (error) {
|
|
2119
|
-
console.log(JSON.stringify({ ok: false, error: error.message }));
|
|
2120
|
-
}
|
|
2121
|
-
NODESCRIPT
|
|
2122
|
-
)
|
|
2123
|
-
|
|
2124
|
-
result=$(echo "$node_script" | node - "$model_name")
|
|
2125
|
-
echo "$result"
|
|
2126
|
-
;;
|
|
2127
|
-
|
|
2128
|
-
*)
|
|
2129
|
-
echo "Usage: model-profile <command> [args]"
|
|
2130
|
-
echo ""
|
|
2131
|
-
echo "Commands:"
|
|
2132
|
-
echo " get <caste> Get model for caste"
|
|
2133
|
-
echo " set <caste> <model> Set user override"
|
|
2134
|
-
echo " reset <caste> Reset user override"
|
|
2135
|
-
echo " list List all assignments"
|
|
2136
|
-
echo " select <caste> <task> [model] Select model with task routing"
|
|
2137
|
-
echo " validate <model> Validate model name"
|
|
2138
|
-
json_err "$E_VALIDATION_FAILED" "Usage: model-profile get <caste>|list|verify|select|validate"
|
|
2139
|
-
;;
|
|
2140
|
-
esac
|
|
2141
|
-
;;
|
|
2142
|
-
|
|
2143
|
-
model-get)
|
|
2144
|
-
# Shortcut: model-get <caste>
|
|
2145
|
-
caste="${1:-}"
|
|
2146
|
-
[[ -z "$caste" ]] && json_err "$E_VALIDATION_FAILED" "Usage: model-get <caste>"
|
|
2147
|
-
|
|
2148
|
-
# Delegate to model-profile get
|
|
2149
|
-
exec bash "$0" model-profile get "$caste"
|
|
2150
|
-
;;
|
|
2151
|
-
|
|
2152
|
-
model-list)
|
|
2153
|
-
# Shortcut: list all models
|
|
2154
|
-
exec bash "$0" model-profile list
|
|
2155
|
-
;;
|
|
2156
|
-
|
|
2157
|
-
# ============================================
|
|
2158
|
-
# CHAMBER UTILITIES (colony lifecycle)
|
|
2159
|
-
# ============================================
|
|
2160
|
-
|
|
2161
|
-
chamber-create)
|
|
2162
|
-
# Create a new chamber (entomb a colony)
|
|
2163
|
-
# Usage: chamber-create <chamber_dir> <state_file> <goal> <phases_completed> <total_phases> <milestone> <version> <decisions_json> <learnings_json>
|
|
2164
|
-
[[ $# -ge 9 ]] || json_err "$E_VALIDATION_FAILED" "Usage: chamber-create <chamber_dir> <state_file> <goal> <phases_completed> <total_phases> <milestone> <version> <decisions_json> <learnings_json>"
|
|
2165
|
-
|
|
2166
|
-
# Check if chamber-utils.sh is available
|
|
2167
|
-
if ! type chamber_create &>/dev/null; then
|
|
2168
|
-
json_err "$E_FILE_NOT_FOUND" "chamber-utils.sh not loaded"
|
|
2169
|
-
fi
|
|
2170
|
-
|
|
2171
|
-
chamber_create "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"
|
|
2172
|
-
;;
|
|
2173
|
-
|
|
2174
|
-
chamber-verify)
|
|
2175
|
-
# Verify chamber integrity
|
|
2176
|
-
# Usage: chamber-verify <chamber_dir>
|
|
2177
|
-
[[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: chamber-verify <chamber_dir>"
|
|
2178
|
-
|
|
2179
|
-
if ! type chamber_verify &>/dev/null; then
|
|
2180
|
-
json_err "$E_FILE_NOT_FOUND" "chamber-utils.sh not loaded"
|
|
2181
|
-
fi
|
|
2182
|
-
|
|
2183
|
-
chamber_verify "$1"
|
|
2184
|
-
;;
|
|
2185
|
-
|
|
2186
|
-
chamber-list)
|
|
2187
|
-
# List all chambers
|
|
2188
|
-
# Usage: chamber-list [chambers_root]
|
|
2189
|
-
chambers_root="${1:-$AETHER_ROOT/.aether/chambers}"
|
|
2190
|
-
|
|
2191
|
-
if ! type chamber_list &>/dev/null; then
|
|
2192
|
-
json_err "$E_FILE_NOT_FOUND" "chamber-utils.sh not loaded"
|
|
2193
|
-
fi
|
|
2194
|
-
|
|
2195
|
-
chamber_list "$chambers_root"
|
|
2196
|
-
;;
|
|
2197
|
-
|
|
2198
|
-
milestone-detect)
|
|
2199
|
-
# Detect colony milestone from state
|
|
2200
|
-
# Usage: milestone-detect
|
|
2201
|
-
# Returns: {ok: true, milestone: "...", version: "...", phases_completed: N, total_phases: N, progress_percent: N}
|
|
2202
|
-
|
|
2203
|
-
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
2204
|
-
|
|
2205
|
-
# Extract and compute milestone data using jq
|
|
2206
|
-
result=$(jq '
|
|
2207
|
-
# Extract key data
|
|
2208
|
-
(.plan.phases // []) as $phases |
|
|
2209
|
-
(.errors.records // []) as $errors |
|
|
2210
|
-
(.milestone // null) as $stored_milestone |
|
|
2211
|
-
|
|
2212
|
-
# Count completed phases
|
|
2213
|
-
([$phases[] | select(.status == "completed")] | length) as $completed_count |
|
|
2214
|
-
($phases | length) as $total_phases |
|
|
2215
|
-
|
|
2216
|
-
# Check for critical errors
|
|
2217
|
-
([$errors[] | select(.severity == "critical")] | length) as $critical_count |
|
|
2218
|
-
|
|
2219
|
-
# Determine milestone based on state
|
|
2220
|
-
if $critical_count > 0 then
|
|
2221
|
-
"Failed Mound"
|
|
2222
|
-
elif $total_phases > 0 and $completed_count == $total_phases then
|
|
2223
|
-
if $stored_milestone == "Crowned Anthill" then
|
|
2224
|
-
"Crowned Anthill"
|
|
2225
|
-
else
|
|
2226
|
-
"Sealed Chambers"
|
|
2227
|
-
end
|
|
2228
|
-
elif $completed_count >= 5 then
|
|
2229
|
-
"Ventilated Nest"
|
|
2230
|
-
elif $completed_count >= 3 then
|
|
2231
|
-
"Brood Stable"
|
|
2232
|
-
elif $completed_count >= 1 then
|
|
2233
|
-
"Open Chambers"
|
|
2234
|
-
else
|
|
2235
|
-
"First Mound"
|
|
2236
|
-
end as $milestone |
|
|
2237
|
-
|
|
2238
|
-
# Compute version: major = floor(total_phases / 10), minor = total_phases % 10, patch = completed_count
|
|
2239
|
-
($total_phases / 10 | floor) as $major |
|
|
2240
|
-
($total_phases % 10) as $minor |
|
|
2241
|
-
$completed_count as $patch |
|
|
2242
|
-
"v\($major).\($minor).\($patch)" as $version |
|
|
2243
|
-
|
|
2244
|
-
# Calculate progress percentage
|
|
2245
|
-
(if $total_phases > 0 then ($completed_count * 100 / $total_phases | round) else 0 end) as $progress |
|
|
2246
|
-
|
|
2247
|
-
# Return result
|
|
2248
|
-
{
|
|
2249
|
-
ok: true,
|
|
2250
|
-
milestone: $milestone,
|
|
2251
|
-
version: $version,
|
|
2252
|
-
phases_completed: $completed_count,
|
|
2253
|
-
total_phases: $total_phases,
|
|
2254
|
-
progress_percent: $progress
|
|
2255
|
-
}
|
|
2256
|
-
' "$DATA_DIR/COLONY_STATE.json")
|
|
2257
|
-
|
|
2258
|
-
echo "$result"
|
|
2259
|
-
;;
|
|
2260
|
-
|
|
2261
|
-
# ============================================
|
|
2262
|
-
# SWARM ACTIVITY TRACKING (colony visualization)
|
|
2263
|
-
# ============================================
|
|
2264
|
-
|
|
2265
|
-
swarm-activity-log)
|
|
2266
|
-
# Log an activity entry for swarm visualization
|
|
2267
|
-
# Usage: swarm-activity-log <ant_name> <action> <details>
|
|
2268
|
-
ant_name="${1:-}"
|
|
2269
|
-
action="${2:-}"
|
|
2270
|
-
details="${3:-}"
|
|
2271
|
-
[[ -z "$ant_name" || -z "$action" || -z "$details" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-activity-log <ant_name> <action> <details>"
|
|
2272
|
-
|
|
2273
|
-
mkdir -p "$DATA_DIR"
|
|
2274
|
-
log_file="$DATA_DIR/swarm-activity.log"
|
|
2275
|
-
ts=$(date -u +"%H:%M:%S")
|
|
2276
|
-
echo "[$ts] $ant_name: $action $details" >> "$log_file"
|
|
2277
|
-
json_ok '"logged"'
|
|
2278
|
-
;;
|
|
2279
|
-
|
|
2280
|
-
swarm-display-init)
|
|
2281
|
-
# Initialize swarm display state file
|
|
2282
|
-
# Usage: swarm-display-init <swarm_id>
|
|
2283
|
-
swarm_id="${1:-swarm-$(date +%s)}"
|
|
2284
|
-
mkdir -p "$DATA_DIR"
|
|
2285
|
-
|
|
2286
|
-
display_file="$DATA_DIR/swarm-display.json"
|
|
2287
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2288
|
-
|
|
2289
|
-
atomic_write "$display_file" "{
|
|
2290
|
-
\"swarm_id\": \"$swarm_id\",
|
|
2291
|
-
\"timestamp\": \"$ts\",
|
|
2292
|
-
\"active_ants\": [],
|
|
2293
|
-
\"summary\": { \"total_active\": 0, \"by_caste\": {}, \"by_zone\": {} },
|
|
2294
|
-
\"chambers\": {
|
|
2295
|
-
\"fungus_garden\": {\"activity\": 0, \"icon\": \"🍄\"},
|
|
2296
|
-
\"nursery\": {\"activity\": 0, \"icon\": \"🥚\"},
|
|
2297
|
-
\"refuse_pile\": {\"activity\": 0, \"icon\": \"🗑️\"},
|
|
2298
|
-
\"throne_room\": {\"activity\": 0, \"icon\": \"👑\"},
|
|
2299
|
-
\"foraging_trail\": {\"activity\": 0, \"icon\": \"🌿\"}
|
|
2300
|
-
}
|
|
2301
|
-
}"
|
|
2302
|
-
json_ok "{\"swarm_id\":\"$swarm_id\",\"initialized\":true}"
|
|
2303
|
-
;;
|
|
2304
|
-
|
|
2305
|
-
swarm-display-update)
|
|
2306
|
-
# Update ant activity in swarm display
|
|
2307
|
-
# Usage: swarm-display-update <ant_name> <caste> <ant_status> <task> [parent] [tools_json] [tokens] [chamber] [progress]
|
|
2308
|
-
ant_name="${1:-}"
|
|
2309
|
-
caste="${2:-}"
|
|
2310
|
-
ant_status="${3:-}"
|
|
2311
|
-
task="${4:-}"
|
|
2312
|
-
parent="${5:-}"
|
|
2313
|
-
tools_json="${6:-}"
|
|
2314
|
-
[[ -z "$tools_json" ]] && tools_json="{}"
|
|
2315
|
-
tokens="${7:-0}"
|
|
2316
|
-
chamber="${8:-}"
|
|
2317
|
-
progress="${9:-0}"
|
|
2318
|
-
|
|
2319
|
-
[[ -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]"
|
|
2320
|
-
|
|
2321
|
-
display_file="$DATA_DIR/swarm-display.json"
|
|
2322
|
-
|
|
2323
|
-
# Initialize if doesn't exist
|
|
2324
|
-
if [[ ! -f "$display_file" ]]; then
|
|
2325
|
-
bash "$0" swarm-display-init "default-swarm" >/dev/null 2>&1
|
|
2326
|
-
fi
|
|
2327
|
-
|
|
2328
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2329
|
-
|
|
2330
|
-
# Read current display and update using jq
|
|
2331
|
-
updated=$(jq --arg ant "$ant_name" --arg caste "$caste" --arg ant_status "$ant_status" \
|
|
2332
|
-
--arg task "$task" --arg parent "$parent" --argjson tools "$tools_json" \
|
|
2333
|
-
--argjson tokens "$tokens" --arg ts "$ts" --arg chamber "$chamber" --argjson progress "$progress" '
|
|
2334
|
-
# Find existing ant or create new entry
|
|
2335
|
-
(.active_ants | map(select(.name == $ant)) | length) as $exists |
|
|
2336
|
-
# Get old chamber if ant exists
|
|
2337
|
-
(if $exists > 0 then
|
|
2338
|
-
(.active_ants[] | select(.name == $ant) | .chamber // "")
|
|
2339
|
-
else
|
|
2340
|
-
""
|
|
2341
|
-
end) as $old_chamber |
|
|
2342
|
-
# Determine new chamber
|
|
2343
|
-
(if $chamber != "" then $chamber else $old_chamber end) as $new_chamber |
|
|
2344
|
-
if $exists > 0 then
|
|
2345
|
-
# Update existing ant
|
|
2346
|
-
.active_ants = [.active_ants[] | if .name == $ant then
|
|
2347
|
-
. + {
|
|
2348
|
-
caste: $caste,
|
|
2349
|
-
status: $ant_status,
|
|
2350
|
-
task: $task,
|
|
2351
|
-
parent: (if $parent != "" then $parent else .parent end),
|
|
2352
|
-
tools: (if $tools != {} then $tools else .tools end),
|
|
2353
|
-
tokens: (.tokens + $tokens),
|
|
2354
|
-
chamber: (if $chamber != "" then $chamber else (.chamber // null) end),
|
|
2355
|
-
progress: (if $progress > 0 then $progress else (.progress // 0) end),
|
|
2356
|
-
updated_at: $ts
|
|
2357
|
-
}
|
|
2358
|
-
else . end]
|
|
2359
|
-
else
|
|
2360
|
-
# Add new ant
|
|
2361
|
-
.active_ants += [{
|
|
2362
|
-
name: $ant,
|
|
2363
|
-
caste: $caste,
|
|
2364
|
-
status: $ant_status,
|
|
2365
|
-
task: $task,
|
|
2366
|
-
parent: (if $parent != "" then $parent else null end),
|
|
2367
|
-
tools: (if $tools != {} then $tools else {read:0,grep:0,edit:0,bash:0} end),
|
|
2368
|
-
tokens: $tokens,
|
|
2369
|
-
chamber: (if $chamber != "" then $chamber else null end),
|
|
2370
|
-
progress: $progress,
|
|
2371
|
-
started_at: $ts,
|
|
2372
|
-
updated_at: $ts
|
|
2373
|
-
}]
|
|
2374
|
-
end |
|
|
2375
|
-
# Recalculate summary
|
|
2376
|
-
.summary.total_active = (.active_ants | length) |
|
|
2377
|
-
.summary.by_caste = (.active_ants | group_by(.caste) | map({key: .[0].caste, value: length}) | from_entries) |
|
|
2378
|
-
.summary.by_zone = (.active_ants | group_by(.status) | map({key: .[0].status, value: length}) | from_entries) |
|
|
2379
|
-
# Update chamber activity counts
|
|
2380
|
-
# Decrement old chamber if changed
|
|
2381
|
-
(if $old_chamber != "" and $old_chamber != $new_chamber and has("chambers") and (.chambers | has($old_chamber)) then
|
|
2382
|
-
.chambers[$old_chamber].activity = [(.chambers[$old_chamber].activity // 1) - 1, 0] | max
|
|
2383
|
-
else
|
|
2384
|
-
.
|
|
2385
|
-
end) |
|
|
2386
|
-
# Increment new chamber
|
|
2387
|
-
(if $new_chamber != "" and has("chambers") and (.chambers | has($new_chamber)) then
|
|
2388
|
-
.chambers[$new_chamber].activity = (.chambers[$new_chamber].activity // 0) + 1
|
|
2389
|
-
else
|
|
2390
|
-
.
|
|
2391
|
-
end)
|
|
2392
|
-
' "$display_file") || json_err "$E_JSON_INVALID" "Failed to update swarm display"
|
|
2393
|
-
|
|
2394
|
-
atomic_write "$display_file" "$updated"
|
|
2395
|
-
|
|
2396
|
-
# Get emoji for response
|
|
2397
|
-
emoji=$(get_caste_emoji "$caste")
|
|
2398
|
-
json_ok "{\"updated\":true,\"ant\":\"$ant_name\",\"caste\":\"$caste\",\"emoji\":\"$emoji\",\"chamber\":\"$chamber\",\"progress\":$progress}"
|
|
2399
|
-
;;
|
|
2400
|
-
|
|
2401
|
-
swarm-display-get)
|
|
2402
|
-
# Get current swarm display state
|
|
2403
|
-
# Usage: swarm-display-get
|
|
2404
|
-
display_file="$DATA_DIR/swarm-display.json"
|
|
2405
|
-
|
|
2406
|
-
if [[ ! -f "$display_file" ]]; then
|
|
2407
|
-
json_ok '{"swarm_id":null,"active_ants":[],"summary":{"total_active":0,"by_caste":{},"by_zone":{}},"chambers":{}}'
|
|
2408
|
-
else
|
|
2409
|
-
json_ok "$(cat "$display_file")"
|
|
2410
|
-
fi
|
|
2411
|
-
;;
|
|
2412
|
-
|
|
2413
|
-
swarm-display-render)
|
|
2414
|
-
# Render the swarm display to terminal
|
|
2415
|
-
# Usage: swarm-display-render [swarm_id]
|
|
2416
|
-
swarm_id="${1:-default-swarm}"
|
|
2417
|
-
|
|
2418
|
-
display_script="$SCRIPT_DIR/utils/swarm-display.sh"
|
|
2419
|
-
|
|
2420
|
-
if [[ -f "$display_script" ]]; then
|
|
2421
|
-
# Execute the display script
|
|
2422
|
-
bash "$display_script" "$swarm_id" 2>/dev/null || true
|
|
2423
|
-
json_ok '{"rendered":true}'
|
|
2424
|
-
else
|
|
2425
|
-
json_err "$E_FILE_NOT_FOUND" "Display script not found: $display_script"
|
|
2426
|
-
fi
|
|
2427
|
-
;;
|
|
2428
|
-
|
|
2429
|
-
swarm-display-inline)
|
|
2430
|
-
# Inline swarm display for Claude Code (no loop, no clear)
|
|
2431
|
-
# Usage: swarm-display-inline [swarm_id]
|
|
2432
|
-
swarm_id="${1:-default-swarm}"
|
|
2433
|
-
display_file="$DATA_DIR/swarm-display.json"
|
|
2434
|
-
|
|
2435
|
-
# ANSI colors
|
|
2436
|
-
BLUE='\033[34m'
|
|
2437
|
-
GREEN='\033[32m'
|
|
2438
|
-
YELLOW='\033[33m'
|
|
2439
|
-
RED='\033[31m'
|
|
2440
|
-
MAGENTA='\033[35m'
|
|
2441
|
-
BOLD='\033[1m'
|
|
2442
|
-
DIM='\033[2m'
|
|
2443
|
-
RESET='\033[0m'
|
|
2444
|
-
|
|
2445
|
-
# Caste colors
|
|
2446
|
-
get_caste_color() {
|
|
2447
|
-
case "$1" in
|
|
2448
|
-
builder) echo "$BLUE" ;;
|
|
2449
|
-
watcher) echo "$GREEN" ;;
|
|
2450
|
-
scout) echo "$YELLOW" ;;
|
|
2451
|
-
chaos) echo "$RED" ;;
|
|
2452
|
-
prime) echo "$MAGENTA" ;;
|
|
2453
|
-
oracle) echo "$MAGENTA" ;;
|
|
2454
|
-
route_setter) echo "$MAGENTA" ;;
|
|
2455
|
-
*) echo "$RESET" ;;
|
|
2456
|
-
esac
|
|
2457
|
-
}
|
|
2458
|
-
|
|
2459
|
-
# Caste emojis with ant
|
|
2460
|
-
get_caste_emoji() {
|
|
2461
|
-
case "$1" in
|
|
2462
|
-
builder) echo "🔨🐜" ;;
|
|
2463
|
-
watcher) echo "👁️🐜" ;;
|
|
2464
|
-
scout) echo "🔍🐜" ;;
|
|
2465
|
-
chaos) echo "🎲🐜" ;;
|
|
2466
|
-
prime) echo "👑🐜" ;;
|
|
2467
|
-
oracle) echo "🔮🐜" ;;
|
|
2468
|
-
route_setter) echo "🧭🐜" ;;
|
|
2469
|
-
archaeologist) echo "🏺🐜" ;;
|
|
2470
|
-
chronicler) echo "📝🐜" ;;
|
|
2471
|
-
gatekeeper) echo "📦🐜" ;;
|
|
2472
|
-
guardian) echo "🛡️🐜" ;;
|
|
2473
|
-
includer) echo "♿🐜" ;;
|
|
2474
|
-
keeper) echo "📚🐜" ;;
|
|
2475
|
-
measurer) echo "⚡🐜" ;;
|
|
2476
|
-
probe) echo "🧪🐜" ;;
|
|
2477
|
-
sage) echo "📜🐜" ;;
|
|
2478
|
-
tracker) echo "🐛🐜" ;;
|
|
2479
|
-
weaver) echo "🔄🐜" ;;
|
|
2480
|
-
colonizer) echo "🌱🐜" ;;
|
|
2481
|
-
dreamer) echo "💭🐜" ;;
|
|
2482
|
-
*) echo "🐜" ;;
|
|
2483
|
-
esac
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
# Status phrases
|
|
2487
|
-
get_status_phrase() {
|
|
2488
|
-
case "$1" in
|
|
2489
|
-
builder) echo "excavating..." ;;
|
|
2490
|
-
watcher) echo "observing..." ;;
|
|
2491
|
-
scout) echo "exploring..." ;;
|
|
2492
|
-
chaos) echo "testing..." ;;
|
|
2493
|
-
prime) echo "coordinating..." ;;
|
|
2494
|
-
oracle) echo "researching..." ;;
|
|
2495
|
-
route_setter) echo "planning..." ;;
|
|
2496
|
-
*) echo "working..." ;;
|
|
2497
|
-
esac
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
# Excavation phrase based on progress
|
|
2501
|
-
get_excavation_phrase() {
|
|
2502
|
-
local progress="${1:-0}"
|
|
2503
|
-
if [[ "$progress" -lt 25 ]]; then
|
|
2504
|
-
echo "🚧 Starting excavation..."
|
|
2505
|
-
elif [[ "$progress" -lt 50 ]]; then
|
|
2506
|
-
echo "⛏️ Digging deeper..."
|
|
2507
|
-
elif [[ "$progress" -lt 75 ]]; then
|
|
2508
|
-
echo "🪨 Moving earth..."
|
|
2509
|
-
elif [[ "$progress" -lt 100 ]]; then
|
|
2510
|
-
echo "🏗️ Almost there..."
|
|
2511
|
-
else
|
|
2512
|
-
echo "✅ Excavation complete!"
|
|
2513
|
-
fi
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
# Format tools: "📖5 🔍3 ✏️2 ⚡1"
|
|
2517
|
-
format_tools() {
|
|
2518
|
-
local read="${1:-0}"
|
|
2519
|
-
local grep="${2:-0}"
|
|
2520
|
-
local edit="${3:-0}"
|
|
2521
|
-
local bash="${4:-0}"
|
|
2522
|
-
local result=""
|
|
2523
|
-
[[ "$read" -gt 0 ]] && result="${result}📖${read} "
|
|
2524
|
-
[[ "$grep" -gt 0 ]] && result="${result}🔍${grep} "
|
|
2525
|
-
[[ "$edit" -gt 0 ]] && result="${result}✏️${edit} "
|
|
2526
|
-
[[ "$bash" -gt 0 ]] && result="${result}⚡${bash}"
|
|
2527
|
-
echo "$result"
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
|
-
# Render progress bar (green when working)
|
|
2531
|
-
render_progress_bar() {
|
|
2532
|
-
local percent="${1:-0}"
|
|
2533
|
-
local width="${2:-20}"
|
|
2534
|
-
[[ "$percent" -lt 0 ]] && percent=0
|
|
2535
|
-
[[ "$percent" -gt 100 ]] && percent=100
|
|
2536
|
-
local filled=$((percent * width / 100))
|
|
2537
|
-
local empty=$((width - filled))
|
|
2538
|
-
local bar=""
|
|
2539
|
-
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
2540
|
-
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
2541
|
-
echo -e "${GREEN}[$bar]${RESET} ${percent}%"
|
|
2542
|
-
}
|
|
2543
|
-
|
|
2544
|
-
# Format duration
|
|
2545
|
-
format_duration() {
|
|
2546
|
-
local seconds="${1:-0}"
|
|
2547
|
-
if [[ "$seconds" -lt 60 ]]; then
|
|
2548
|
-
echo "${seconds}s"
|
|
2549
|
-
else
|
|
2550
|
-
local mins=$((seconds / 60))
|
|
2551
|
-
local secs=$((seconds % 60))
|
|
2552
|
-
echo "${mins}m${secs}s"
|
|
2553
|
-
fi
|
|
2554
|
-
}
|
|
2555
|
-
|
|
2556
|
-
# Check for display file
|
|
2557
|
-
if [[ ! -f "$display_file" ]]; then
|
|
2558
|
-
echo -e "${DIM}🐜 No active swarm data${RESET}"
|
|
2559
|
-
json_ok '{"displayed":false,"reason":"no_data"}'
|
|
2560
|
-
exit 0
|
|
2561
|
-
fi
|
|
2562
|
-
|
|
2563
|
-
# Check for jq
|
|
2564
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
2565
|
-
echo -e "${DIM}🐜 Swarm active (jq not available for details)${RESET}"
|
|
2566
|
-
json_ok '{"displayed":true,"warning":"jq_missing"}'
|
|
2567
|
-
exit 0
|
|
2568
|
-
fi
|
|
2569
|
-
|
|
2570
|
-
# Read swarm data
|
|
2571
|
-
total_active=$(jq -r '.summary.total_active // 0' "$display_file" 2>/dev/null || echo "0")
|
|
2572
|
-
|
|
2573
|
-
if [[ "$total_active" -eq 0 ]]; then
|
|
2574
|
-
echo -e "${DIM}🐜 Colony idle${RESET}"
|
|
2575
|
-
json_ok '{"displayed":true,"ants":0}'
|
|
2576
|
-
exit 0
|
|
2577
|
-
fi
|
|
2578
|
-
|
|
2579
|
-
# Render header with ant logo
|
|
2580
|
-
echo ""
|
|
2581
|
-
cat << 'ANTLOGO'
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
▁▐▖ ▁
|
|
2585
|
-
▗▇▇███▆▇▃▅████▆▇▆▅▟██▛▇
|
|
2586
|
-
▝▜▅▛██████████████▜▅██
|
|
2587
|
-
▁▂▀▇▆██▙▜██████████▛▟███▛▁▃▁
|
|
2588
|
-
▕▂▁▉▅████▙▞██████▜█▚▟████▅▊ ▐
|
|
2589
|
-
▗▁▐█▀▜████▛▃▝▁████▍▘▟▜████▛▀█▂ ▖
|
|
2590
|
-
▁▎▝█▁▝▍▆▜████▊▐▀▏▀▍▂▂▝▀▕▀▌█████▀▅▐▚ █▏▁▁
|
|
2591
|
-
▂▚▃▇▙█▟████▛▏ ▝▜▐▛▀▍▛▘ ▕█████▆▊▐▂▃▞▂▔
|
|
2592
|
-
▚▔█▛██████▙▟▍▜▍▜▃▃▖▟▛▐██████▛▛▜▔▔▞
|
|
2593
|
-
▋▖▍▊▖██████▇▃▁▝██▘▝▃████▜█▜ ▋▐▐▗
|
|
2594
|
-
▍▌▇█▅▂▜██████████████████▉▃▄▋▖ ▝
|
|
2595
|
-
▁▎▍▁▜▟███▀▀▜████████████▛▀▀███▆▂ ▁▁
|
|
2596
|
-
██ ▆▇▌▁▕▚▅▆███▛████████▜███▆▄▞▁▁▐▅▎ █▉
|
|
2597
|
-
▆█████▛▃▟█▀████████████████▛█▙▙▜▉▟▛▜█▌▗
|
|
2598
|
-
▅▆▋ ▁▁▁▔▕▁▁▁▇█████▛▀▀▀▁▜▇▇▁▁▁▁▁▁▁▁ ▐▊▗
|
|
2599
|
-
▗▆▃▃▃▔███▖▔██▀▀▝▀██▀▍█▛▁▐█▏█▛▀▀▏█▛▀▜█▆▃▃▆▖
|
|
2600
|
-
▝▗▖ ▟█▟█▙ █▛▀▘ █▊ ▕█▛▀▜█▏█▛▀▘ █▋▆█▛ ▗▖
|
|
2601
|
-
▘ ▘ ▟▛ ▝▀▘▀▀▀▀▘ ▀▀▂▂█▙▂▐▀▏▀▀▀▀▘▀▘ ▝▀▅▂▝ ▕▏
|
|
2602
|
-
▕▕ ▃▗▄▔▗▄▄▗▗▗▔▄▄▄▄▗▄▄▗▔▃▃▃▗▄▂▄▃▗▄▂▖▖ ▏▁
|
|
2603
|
-
▝▘▏ ▔▔ ▁▔▁▔▔▁▔▔▔▔▔▔▔▁▁ ▔▔ ▔▔▔▔
|
|
2604
|
-
▀ ▀▝▘▀▀▔▘▘▀▝▕▀▀▝▝▀▔▀ ▀▔▘
|
|
2605
|
-
▘ ▗▅▁▝▚▃▀▆▟██▙▆▝▃ ▘ ▁▗▌
|
|
2606
|
-
▔▀▔▝ ▔▀▟▜▛▛▀▔ ▀
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
ANTLOGO
|
|
2610
|
-
echo -e "${BOLD}AETHER COLONY :: Colony Activity${RESET}"
|
|
2611
|
-
echo -e "${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
2612
|
-
echo ""
|
|
2613
|
-
|
|
2614
|
-
# Render each active ant (limit to 5)
|
|
2615
|
-
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
|
|
2616
|
-
color=$(get_caste_color "$ant_caste")
|
|
2617
|
-
emoji=$(get_caste_emoji "$ant_caste")
|
|
2618
|
-
phrase=$(get_status_phrase "$ant_caste")
|
|
2619
|
-
|
|
2620
|
-
# Format tools
|
|
2621
|
-
tools_str=$(format_tools "$read_ct" "$grep_ct" "$edit_ct" "$bash_ct")
|
|
2622
|
-
|
|
2623
|
-
# Truncate task if too long
|
|
2624
|
-
display_task="$ant_task"
|
|
2625
|
-
[[ ${#display_task} -gt 35 ]] && display_task="${display_task:0:32}..."
|
|
2626
|
-
|
|
2627
|
-
# Calculate elapsed time
|
|
2628
|
-
elapsed_str=""
|
|
2629
|
-
started_ts="${started_at:-}"
|
|
2630
|
-
if [[ -n "$started_ts" ]] && [[ "$started_ts" != "null" ]]; then
|
|
2631
|
-
started_ts=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$started_ts" +%s 2>/dev/null)
|
|
2632
|
-
if [[ -z "$started_ts" ]] || [[ "$started_ts" == "null" ]]; then
|
|
2633
|
-
started_ts=$(date -d "$started_ts" +%s 2>/dev/null) || started_ts=0
|
|
2634
|
-
fi
|
|
2635
|
-
now_ts=$(date +%s)
|
|
2636
|
-
elapsed=0
|
|
2637
|
-
if [[ -n "$started_ts" ]] && [[ "$started_ts" -gt 0 ]] 2>/dev/null; then
|
|
2638
|
-
elapsed=$((now_ts - started_ts))
|
|
2639
|
-
fi
|
|
2640
|
-
if [[ ${elapsed:-0} -gt 0 ]]; then
|
|
2641
|
-
elapsed_str="($(format_duration $elapsed))"
|
|
2642
|
-
fi
|
|
2643
|
-
fi
|
|
2644
|
-
|
|
2645
|
-
# Token indicator
|
|
2646
|
-
token_str=""
|
|
2647
|
-
if [[ -n "$tokens" ]] && [[ "$tokens" -gt 0 ]]; then
|
|
2648
|
-
token_str="🍯${tokens}"
|
|
2649
|
-
fi
|
|
2650
|
-
|
|
2651
|
-
# Output ant line: "🐜 Builder: excavating... Implement auth 📖5 🔍3 (2m3s) 🍯1250"
|
|
2652
|
-
echo -e "${color}${emoji} ${BOLD}${ant_name}${RESET}${color}: ${phrase}${RESET} ${display_task}"
|
|
2653
|
-
echo -e " ${tools_str} ${DIM}${elapsed_str}${RESET} ${token_str}"
|
|
2654
|
-
|
|
2655
|
-
# Show progress bar if progress > 0
|
|
2656
|
-
if [[ -n "$progress" ]] && [[ "$progress" -gt 0 ]]; then
|
|
2657
|
-
progress_bar=$(render_progress_bar "$progress" 15)
|
|
2658
|
-
excavation_phrase=$(get_excavation_phrase "$progress")
|
|
2659
|
-
echo -e " ${DIM}${progress_bar}${RESET}"
|
|
2660
|
-
echo -e " ${DIM}${excavation_phrase}${RESET}"
|
|
2661
|
-
fi
|
|
2662
|
-
|
|
2663
|
-
echo ""
|
|
2664
|
-
done
|
|
2665
|
-
|
|
2666
|
-
# Chamber activity map
|
|
2667
|
-
echo -e "${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
|
|
2668
|
-
echo ""
|
|
2669
|
-
echo -e "${BOLD}Chamber Activity:${RESET}"
|
|
2670
|
-
|
|
2671
|
-
# Show active chambers with fire intensity
|
|
2672
|
-
has_chamber_activity=0
|
|
2673
|
-
jq -r '.chambers | to_entries[] | "\(.key)|\(.value.activity)|\(.value.icon)"' "$display_file" 2>/dev/null | \
|
|
2674
|
-
while IFS='|' read -r chamber activity icon; do
|
|
2675
|
-
if [[ -n "$activity" ]] && [[ "$activity" -gt 0 ]]; then
|
|
2676
|
-
has_chamber_activity=1
|
|
2677
|
-
if [[ "$activity" -ge 5 ]]; then
|
|
2678
|
-
fires="🔥🔥🔥"
|
|
2679
|
-
elif [[ "$activity" -ge 3 ]]; then
|
|
2680
|
-
fires="🔥🔥"
|
|
2681
|
-
else
|
|
2682
|
-
fires="🔥"
|
|
2683
|
-
fi
|
|
2684
|
-
chamber_name="${chamber//_/ }"
|
|
2685
|
-
echo -e " ${icon} ${chamber_name} ${fires} (${activity} ants)"
|
|
2686
|
-
fi
|
|
2687
|
-
done
|
|
2688
|
-
|
|
2689
|
-
if [[ "$has_chamber_activity" -eq 0 ]]; then
|
|
2690
|
-
echo -e "${DIM} (no chamber activity)${RESET}"
|
|
2691
|
-
fi
|
|
2692
|
-
|
|
2693
|
-
# Summary
|
|
2694
|
-
echo ""
|
|
2695
|
-
echo -e "${DIM}${total_active} forager$([[ "$total_active" -eq 1 ]] || echo "s") excavating...${RESET}"
|
|
2696
|
-
|
|
2697
|
-
json_ok "{\"displayed\":true,\"ants\":$total_active}"
|
|
2698
|
-
;;
|
|
2699
|
-
|
|
2700
|
-
swarm-timing-start)
|
|
2701
|
-
# Record start time for an ant
|
|
2702
|
-
# Usage: swarm-timing-start <ant_name>
|
|
2703
|
-
ant_name="${1:-}"
|
|
2704
|
-
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-start <ant_name>"
|
|
2705
|
-
|
|
2706
|
-
mkdir -p "$DATA_DIR"
|
|
2707
|
-
timing_file="$DATA_DIR/timing.log"
|
|
2708
|
-
ts=$(date +%s)
|
|
2709
|
-
ts_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2710
|
-
|
|
2711
|
-
# Remove any existing entry for this ant and append new one
|
|
2712
|
-
if [[ -f "$timing_file" ]]; then
|
|
2713
|
-
grep -v "^$ant_name|" "$timing_file" > "${timing_file}.tmp" 2>/dev/null || true
|
|
2714
|
-
mv "${timing_file}.tmp" "$timing_file"
|
|
2715
|
-
fi
|
|
2716
|
-
echo "$ant_name|$ts|$ts_iso" >> "$timing_file"
|
|
2717
|
-
|
|
2718
|
-
json_ok "{\"ant\":\"$ant_name\",\"started_at\":\"$ts_iso\",\"timestamp\":$ts}"
|
|
2719
|
-
;;
|
|
2720
|
-
|
|
2721
|
-
swarm-timing-get)
|
|
2722
|
-
# Get elapsed time for an ant
|
|
2723
|
-
# Usage: swarm-timing-get <ant_name>
|
|
2724
|
-
ant_name="${1:-}"
|
|
2725
|
-
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-get <ant_name>"
|
|
2726
|
-
|
|
2727
|
-
timing_file="$DATA_DIR/timing.log"
|
|
2728
|
-
|
|
2729
|
-
if [[ ! -f "$timing_file" ]] || ! grep -q "^$ant_name|" "$timing_file" 2>/dev/null; then
|
|
2730
|
-
json_ok "{\"ant\":\"$ant_name\",\"started_at\":null,\"elapsed_seconds\":0,\"elapsed_formatted\":\"00:00\"}"
|
|
2731
|
-
exit 0
|
|
2732
|
-
fi
|
|
2733
|
-
|
|
2734
|
-
# Read start time
|
|
2735
|
-
start_line=$(grep "^$ant_name|" "$timing_file" | tail -1)
|
|
2736
|
-
start_ts=$(echo "$start_line" | cut -d'|' -f2)
|
|
2737
|
-
start_iso=$(echo "$start_line" | cut -d'|' -f3)
|
|
2738
|
-
|
|
2739
|
-
now=$(date +%s)
|
|
2740
|
-
elapsed=$((now - start_ts))
|
|
2741
|
-
|
|
2742
|
-
# Format as MM:SS
|
|
2743
|
-
mins=$((elapsed / 60))
|
|
2744
|
-
secs=$((elapsed % 60))
|
|
2745
|
-
formatted=$(printf "%02d:%02d" $mins $secs)
|
|
2746
|
-
|
|
2747
|
-
json_ok "{\"ant\":\"$ant_name\",\"started_at\":\"$start_iso\",\"elapsed_seconds\":$elapsed,\"elapsed_formatted\":\"$formatted\"}"
|
|
2748
|
-
;;
|
|
2749
|
-
|
|
2750
|
-
swarm-timing-eta)
|
|
2751
|
-
# Calculate ETA based on progress percentage
|
|
2752
|
-
# Usage: swarm-timing-eta <ant_name> <percent_complete>
|
|
2753
|
-
ant_name="${1:-}"
|
|
2754
|
-
percent="${2:-0}"
|
|
2755
|
-
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-eta <ant_name> <percent_complete>"
|
|
2756
|
-
|
|
2757
|
-
# Validate percent is a number
|
|
2758
|
-
if ! [[ "$percent" =~ ^[0-9]+$ ]]; then
|
|
2759
|
-
percent=0
|
|
2760
|
-
fi
|
|
2761
|
-
|
|
2762
|
-
# Clamp percent to 0-100
|
|
2763
|
-
if [[ $percent -lt 0 ]]; then
|
|
2764
|
-
percent=0
|
|
2765
|
-
elif [[ $percent -gt 100 ]]; then
|
|
2766
|
-
percent=100
|
|
2767
|
-
fi
|
|
2768
|
-
|
|
2769
|
-
timing_file="$DATA_DIR/timing.log"
|
|
2770
|
-
|
|
2771
|
-
if [[ ! -f "$timing_file" ]] || ! grep -q "^$ant_name|" "$timing_file" 2>/dev/null; then
|
|
2772
|
-
json_ok "{\"ant\":\"$ant_name\",\"percent\":$percent,\"eta_seconds\":null,\"eta_formatted\":\"--:--\"}"
|
|
2773
|
-
exit 0
|
|
2774
|
-
fi
|
|
2775
|
-
|
|
2776
|
-
# Read start time
|
|
2777
|
-
start_ts=$(grep "^$ant_name|" "$timing_file" | tail -1 | cut -d'|' -f2)
|
|
2778
|
-
now=$(date +%s)
|
|
2779
|
-
elapsed=$((now - start_ts))
|
|
2780
|
-
|
|
2781
|
-
# Calculate ETA
|
|
2782
|
-
if [[ $percent -le 0 ]]; then
|
|
2783
|
-
eta_seconds=null
|
|
2784
|
-
eta_formatted="--:--"
|
|
2785
|
-
elif [[ $percent -ge 100 ]]; then
|
|
2786
|
-
eta_seconds=0
|
|
2787
|
-
eta_formatted="00:00"
|
|
2788
|
-
else
|
|
2789
|
-
# ETA = (elapsed / percent) * (100 - percent)
|
|
2790
|
-
eta_seconds=$(( (elapsed * (100 - percent)) / percent ))
|
|
2791
|
-
mins=$((eta_seconds / 60))
|
|
2792
|
-
secs=$((eta_seconds % 60))
|
|
2793
|
-
eta_formatted=$(printf "%02d:%02d" $mins $secs)
|
|
2794
|
-
fi
|
|
2795
|
-
|
|
2796
|
-
json_ok "{\"ant\":\"$ant_name\",\"percent\":$percent,\"eta_seconds\":$eta_seconds,\"eta_formatted\":\"$eta_formatted\"}"
|
|
2797
|
-
;;
|
|
2798
|
-
|
|
2799
|
-
# ============================================
|
|
2800
|
-
# VIEW STATE MANAGEMENT (collapsible views)
|
|
2801
|
-
# ============================================
|
|
2802
|
-
|
|
2803
|
-
view-state-init)
|
|
2804
|
-
# Initialize view state file with default structure
|
|
2805
|
-
# Usage: view-state-init
|
|
2806
|
-
mkdir -p "$DATA_DIR"
|
|
2807
|
-
view_state_file="$DATA_DIR/view-state.json"
|
|
2808
|
-
|
|
2809
|
-
if [[ ! -f "$view_state_file" ]]; then
|
|
2810
|
-
atomic_write "$view_state_file" '{
|
|
2811
|
-
"version": "1.0",
|
|
2812
|
-
"swarm_display": {
|
|
2813
|
-
"expanded": [],
|
|
2814
|
-
"collapsed": [],
|
|
2815
|
-
"default_expand_depth": 2
|
|
2816
|
-
},
|
|
2817
|
-
"tunnel_view": {
|
|
2818
|
-
"expanded": [],
|
|
2819
|
-
"collapsed": ["__depth_3_plus__"],
|
|
2820
|
-
"default_expand_depth": 2,
|
|
2821
|
-
"show_completed": true
|
|
2822
|
-
}
|
|
2823
|
-
}'
|
|
2824
|
-
json_ok '{"initialized":true,"file":"view-state.json"}'
|
|
2825
|
-
else
|
|
2826
|
-
json_ok '{"initialized":false,"file":"view-state.json","exists":true}'
|
|
2827
|
-
fi
|
|
2828
|
-
;;
|
|
2829
|
-
|
|
2830
|
-
view-state-get)
|
|
2831
|
-
# Get view state or specific key
|
|
2832
|
-
# Usage: view-state-get [view_name] [key]
|
|
2833
|
-
view_name="${1:-}"
|
|
2834
|
-
key="${2:-}"
|
|
2835
|
-
view_state_file="$DATA_DIR/view-state.json"
|
|
2836
|
-
|
|
2837
|
-
if [[ ! -f "$view_state_file" ]]; then
|
|
2838
|
-
# Auto-initialize if not exists
|
|
2839
|
-
bash "$0" view-state-init >/dev/null 2>&1
|
|
2840
|
-
fi
|
|
2841
|
-
|
|
2842
|
-
if [[ -z "$view_name" ]]; then
|
|
2843
|
-
# Return entire state
|
|
2844
|
-
json_ok "$(cat "$view_state_file")"
|
|
2845
|
-
elif [[ -z "$key" ]]; then
|
|
2846
|
-
# Return specific view
|
|
2847
|
-
json_ok "$(jq ".${view_name} // {}" "$view_state_file")"
|
|
2848
|
-
else
|
|
2849
|
-
# Return specific key from view
|
|
2850
|
-
json_ok "$(jq ".${view_name}.${key} // null" "$view_state_file")"
|
|
2851
|
-
fi
|
|
2852
|
-
;;
|
|
2853
|
-
|
|
2854
|
-
view-state-set)
|
|
2855
|
-
# Set a specific key in a view
|
|
2856
|
-
# Usage: view-state-set <view_name> <key> <value>
|
|
2857
|
-
view_name="${1:-}"
|
|
2858
|
-
key="${2:-}"
|
|
2859
|
-
value="${3:-}"
|
|
2860
|
-
[[ -z "$view_name" || -z "$key" ]] && json_err "$E_VALIDATION_FAILED" "Usage: view-state-set <view_name> <key> <value>"
|
|
2861
|
-
|
|
2862
|
-
view_state_file="$DATA_DIR/view-state.json"
|
|
2863
|
-
|
|
2864
|
-
if [[ ! -f "$view_state_file" ]]; then
|
|
2865
|
-
bash "$0" view-state-init >/dev/null 2>&1
|
|
2866
|
-
fi
|
|
2867
|
-
|
|
2868
|
-
# Determine if value is JSON or string
|
|
2869
|
-
if [[ "$value" =~ ^\[.*\]$ ]] || [[ "$value" =~ ^\{.*\}$ ]] || [[ "$value" =~ ^(true|false|null|[0-9]+)$ ]]; then
|
|
2870
|
-
# Value appears to be JSON - use as-is
|
|
2871
|
-
updated=$(jq --arg view "$view_name" --arg key "$key" --argjson val "$value" '
|
|
2872
|
-
.[$view][$key] = $val
|
|
2873
|
-
' "$view_state_file") || json_err "$E_JSON_INVALID" "Failed to update view state"
|
|
2874
|
-
else
|
|
2875
|
-
# Treat as string
|
|
2876
|
-
updated=$(jq --arg view "$view_name" --arg key "$key" --arg val "$value" '
|
|
2877
|
-
.[$view][$key] = $val
|
|
2878
|
-
' "$view_state_file") || json_err "$E_JSON_INVALID" "Failed to update view state"
|
|
2879
|
-
fi
|
|
2880
|
-
|
|
2881
|
-
atomic_write "$view_state_file" "$updated"
|
|
2882
|
-
json_ok "$(echo "$updated" | jq ".${view_name}")"
|
|
2883
|
-
;;
|
|
2884
|
-
|
|
2885
|
-
view-state-toggle)
|
|
2886
|
-
# Toggle item between expanded and collapsed
|
|
2887
|
-
# Usage: view-state-toggle <view_name> <item>
|
|
2888
|
-
view_name="${1:-}"
|
|
2889
|
-
item="${2:-}"
|
|
2890
|
-
[[ -z "$view_name" || -z "$item" ]] && json_err "$E_VALIDATION_FAILED" "Usage: view-state-toggle <view_name> <item>"
|
|
2891
|
-
|
|
2892
|
-
view_state_file="$DATA_DIR/view-state.json"
|
|
2893
|
-
|
|
2894
|
-
if [[ ! -f "$view_state_file" ]]; then
|
|
2895
|
-
bash "$0" view-state-init >/dev/null 2>&1
|
|
2896
|
-
fi
|
|
2897
|
-
|
|
2898
|
-
# Check current state
|
|
2899
|
-
is_expanded=$(jq --arg view "$view_name" --arg item "$item" '
|
|
2900
|
-
.[$view].expanded | contains([$item])
|
|
2901
|
-
' "$view_state_file")
|
|
2902
|
-
|
|
2903
|
-
if [[ "$is_expanded" == "true" ]]; then
|
|
2904
|
-
# Move from expanded to collapsed
|
|
2905
|
-
updated=$(jq --arg view "$view_name" --arg item "$item" '
|
|
2906
|
-
.[$view].expanded -= [$item] |
|
|
2907
|
-
.[$view].collapsed += [$item]
|
|
2908
|
-
' "$view_state_file")
|
|
2909
|
-
new_state="collapsed"
|
|
2910
|
-
else
|
|
2911
|
-
# Move from collapsed to expanded
|
|
2912
|
-
updated=$(jq --arg view "$view_name" --arg item "$item" '
|
|
2913
|
-
.[$view].collapsed -= [$item] |
|
|
2914
|
-
.[$view].expanded += [$item]
|
|
2915
|
-
' "$view_state_file")
|
|
2916
|
-
new_state="expanded"
|
|
2917
|
-
fi
|
|
2918
|
-
|
|
2919
|
-
atomic_write "$view_state_file" "$updated"
|
|
2920
|
-
json_ok "{\"item\":\"$item\",\"state\":\"$new_state\",\"view\":\"$view_name\"}"
|
|
2921
|
-
;;
|
|
2922
|
-
|
|
2923
|
-
view-state-expand)
|
|
2924
|
-
# Explicitly expand an item
|
|
2925
|
-
# Usage: view-state-expand <view_name> <item>
|
|
2926
|
-
view_name="${1:-}"
|
|
2927
|
-
item="${2:-}"
|
|
2928
|
-
[[ -z "$view_name" || -z "$item" ]] && json_err "$E_VALIDATION_FAILED" "Usage: view-state-expand <view_name> <item>"
|
|
2929
|
-
|
|
2930
|
-
view_state_file="$DATA_DIR/view-state.json"
|
|
2931
|
-
|
|
2932
|
-
if [[ ! -f "$view_state_file" ]]; then
|
|
2933
|
-
bash "$0" view-state-init >/dev/null 2>&1
|
|
2934
|
-
fi
|
|
2935
|
-
|
|
2936
|
-
updated=$(jq --arg view "$view_name" --arg item "$item" '
|
|
2937
|
-
.[$view].collapsed -= [$item] |
|
|
2938
|
-
.[$view].expanded += [$item]
|
|
2939
|
-
' "$view_state_file") || json_err "$E_JSON_INVALID" "Failed to update view state"
|
|
2940
|
-
|
|
2941
|
-
atomic_write "$view_state_file" "$updated"
|
|
2942
|
-
json_ok "{\"item\":\"$item\",\"state\":\"expanded\",\"view\":\"$view_name\"}"
|
|
2943
|
-
;;
|
|
2944
|
-
|
|
2945
|
-
view-state-collapse)
|
|
2946
|
-
# Explicitly collapse an item
|
|
2947
|
-
# Usage: view-state-collapse <view_name> <item>
|
|
2948
|
-
view_name="${1:-}"
|
|
2949
|
-
item="${2:-}"
|
|
2950
|
-
[[ -z "$view_name" || -z "$item" ]] && json_err "$E_VALIDATION_FAILED" "Usage: view-state-collapse <view_name> <item>"
|
|
2951
|
-
|
|
2952
|
-
view_state_file="$DATA_DIR/view-state.json"
|
|
2953
|
-
|
|
2954
|
-
if [[ ! -f "$view_state_file" ]]; then
|
|
2955
|
-
bash "$0" view-state-init >/dev/null 2>&1
|
|
2956
|
-
fi
|
|
2957
|
-
|
|
2958
|
-
updated=$(jq --arg view "$view_name" --arg item "$item" '
|
|
2959
|
-
.[$view].expanded -= [$item] |
|
|
2960
|
-
.[$view].collapsed += [$item]
|
|
2961
|
-
' "$view_state_file") || json_err "$E_JSON_INVALID" "Failed to update view state"
|
|
2962
|
-
|
|
2963
|
-
atomic_write "$view_state_file" "$updated"
|
|
2964
|
-
json_ok "{\"item\":\"$item\",\"state\":\"collapsed\",\"view\":\"$view_name\"}"
|
|
2965
|
-
;;
|
|
2966
|
-
|
|
2967
|
-
queen-init)
|
|
2968
|
-
# Initialize QUEEN.md from template
|
|
2969
|
-
# Creates .aether/QUEEN.md from template if missing
|
|
2970
|
-
queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
|
|
2971
|
-
|
|
2972
|
-
# Check multiple locations for template
|
|
2973
|
-
# Order: dev (runtime/) -> npm install (hub) -> legacy
|
|
2974
|
-
template_file=""
|
|
2975
|
-
for path in \
|
|
2976
|
-
"$AETHER_ROOT/runtime/templates/QUEEN.md.template" \
|
|
2977
|
-
"$HOME/.aether/templates/QUEEN.md.template" \
|
|
2978
|
-
"$AETHER_ROOT/.aether/templates/QUEEN.md.template"; do
|
|
2979
|
-
if [[ -f "$path" ]]; then
|
|
2980
|
-
template_file="$path"
|
|
2981
|
-
break
|
|
2982
|
-
fi
|
|
2983
|
-
done
|
|
2984
|
-
|
|
2985
|
-
# Ensure docs directory exists
|
|
2986
|
-
mkdir -p "$AETHER_ROOT/.aether/docs"
|
|
2987
|
-
|
|
2988
|
-
# Check if QUEEN.md already exists and has content
|
|
2989
|
-
if [[ -f "$queen_file" ]] && [[ -s "$queen_file" ]]; then
|
|
2990
|
-
json_ok '{"created":false,"path":".aether/docs/QUEEN.md","reason":"already_exists"}'
|
|
2991
|
-
exit 0
|
|
2992
|
-
fi
|
|
2993
|
-
|
|
2994
|
-
# Check if template was found
|
|
2995
|
-
if [[ -z "$template_file" ]]; then
|
|
2996
|
-
json_err "$E_FILE_NOT_FOUND" "Template not found" '{"templates_checked":["runtime/templates/QUEEN.md.template","~/.aether/templates/QUEEN.md.template",".aether/templates/QUEEN.md.template"]}'
|
|
2997
|
-
exit 1
|
|
2998
|
-
fi
|
|
2999
|
-
|
|
3000
|
-
# Create QUEEN.md from template with timestamp substitution
|
|
3001
|
-
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
3002
|
-
sed -e "s/{TIMESTAMP}/$timestamp/g" "$template_file" > "$queen_file"
|
|
3003
|
-
|
|
3004
|
-
if [[ -f "$queen_file" ]]; then
|
|
3005
|
-
json_ok "{\"created\":true,\"path\":\".aether/docs/QUEEN.md\",\"source\":\"$template_file\"}"
|
|
3006
|
-
else
|
|
3007
|
-
json_err "$E_FILE_NOT_FOUND" "Failed to create QUEEN.md" '{"path":".aether/docs/QUEEN.md"}'
|
|
3008
|
-
exit 1
|
|
3009
|
-
fi
|
|
3010
|
-
;;
|
|
3011
|
-
|
|
3012
|
-
queen-read)
|
|
3013
|
-
# Read QUEEN.md and return wisdom as JSON for worker priming
|
|
3014
|
-
# Extracts METADATA block and sections for colony guidance
|
|
3015
|
-
queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
|
|
3016
|
-
|
|
3017
|
-
# Check if QUEEN.md exists
|
|
3018
|
-
if [[ ! -f "$queen_file" ]]; then
|
|
3019
|
-
json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/docs/QUEEN.md"}'
|
|
3020
|
-
exit 1
|
|
3021
|
-
fi
|
|
3022
|
-
|
|
3023
|
-
# Extract METADATA JSON block (between <!-- METADATA and -->)
|
|
3024
|
-
metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
|
|
3025
|
-
|
|
3026
|
-
# If no metadata found, return empty structure
|
|
3027
|
-
if [[ -z "$metadata" ]]; then
|
|
3028
|
-
metadata='{"version":"unknown","last_evolved":null,"colonies_contributed":[],"promotion_thresholds":{},"stats":{}}'
|
|
3029
|
-
fi
|
|
3030
|
-
|
|
3031
|
-
# Extract sections content for worker priming
|
|
3032
|
-
# Use awk to parse markdown sections - remove header line and trailing section header
|
|
3033
|
-
philosophies=$(awk '/^## 📜 Philosophies$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
3034
|
-
patterns=$(awk '/^## 🧭 Patterns$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
3035
|
-
redirects=$(awk '/^## ⚠️ Redirects$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
3036
|
-
stack_wisdom=$(awk '/^## 🔧 Stack Wisdom$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
3037
|
-
decrees=$(awk '/^## 🏛️ Decrees$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
3038
|
-
|
|
3039
|
-
# Build JSON output
|
|
3040
|
-
result=$(jq -n \
|
|
3041
|
-
--argjson meta "$metadata" \
|
|
3042
|
-
--arg philosophies "$philosophies" \
|
|
3043
|
-
--arg patterns "$patterns" \
|
|
3044
|
-
--arg redirects "$redirects" \
|
|
3045
|
-
--arg stack_wisdom "$stack_wisdom" \
|
|
3046
|
-
--arg decrees "$decrees" \
|
|
3047
|
-
'{
|
|
3048
|
-
metadata: $meta,
|
|
3049
|
-
wisdom: {
|
|
3050
|
-
philosophies: $philosophies,
|
|
3051
|
-
patterns: $patterns,
|
|
3052
|
-
redirects: $redirects,
|
|
3053
|
-
stack_wisdom: $stack_wisdom,
|
|
3054
|
-
decrees: $decrees
|
|
3055
|
-
},
|
|
3056
|
-
priming: {
|
|
3057
|
-
has_philosophies: ($philosophies | length) > 0 and $philosophies != "*No philosophies recorded yet.*\n",
|
|
3058
|
-
has_patterns: ($patterns | length) > 0 and $patterns != "*No patterns recorded yet.*\n",
|
|
3059
|
-
has_redirects: ($redirects | length) > 0 and $redirects != "*No redirects recorded yet.*\n",
|
|
3060
|
-
has_stack_wisdom: ($stack_wisdom | length) > 0 and $stack_wisdom != "*No stack wisdom recorded yet.*\n",
|
|
3061
|
-
has_decrees: ($decrees | length) > 0 and $decrees != "*No decrees recorded yet.*\n"
|
|
3062
|
-
}
|
|
3063
|
-
}')
|
|
3064
|
-
|
|
3065
|
-
json_ok "$result"
|
|
3066
|
-
;;
|
|
3067
|
-
|
|
3068
|
-
queen-promote)
|
|
3069
|
-
# Promote a learning to QUEEN.md wisdom
|
|
3070
|
-
# Usage: queen-promote <type> <content> <colony_name>
|
|
3071
|
-
# Types: philosophy, pattern, redirect, stack, decree
|
|
3072
|
-
wisdom_type="${1:-}"
|
|
3073
|
-
content="${2:-}"
|
|
3074
|
-
colony_name="${3:-}"
|
|
3075
|
-
|
|
3076
|
-
# Validate required arguments
|
|
3077
|
-
[[ -z "$wisdom_type" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"type"}'
|
|
3078
|
-
[[ -z "$content" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"content"}'
|
|
3079
|
-
[[ -z "$colony_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"colony_name"}'
|
|
3080
|
-
|
|
3081
|
-
# Validate type
|
|
3082
|
-
valid_types=("philosophy" "pattern" "redirect" "stack" "decree")
|
|
3083
|
-
type_valid=false
|
|
3084
|
-
for vt in "${valid_types[@]}"; do
|
|
3085
|
-
[[ "$wisdom_type" == "$vt" ]] && type_valid=true && break
|
|
3086
|
-
done
|
|
3087
|
-
[[ "$type_valid" == "false" ]] && json_err "$E_VALIDATION_FAILED" "Invalid type: $wisdom_type" '{"valid_types":["philosophy","pattern","redirect","stack","decree"]}'
|
|
3088
|
-
|
|
3089
|
-
queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
|
|
3090
|
-
|
|
3091
|
-
# Check if QUEEN.md exists
|
|
3092
|
-
if [[ ! -f "$queen_file" ]]; then
|
|
3093
|
-
json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/docs/QUEEN.md"}'
|
|
3094
|
-
exit 1
|
|
3095
|
-
fi
|
|
3096
|
-
|
|
3097
|
-
# Extract METADATA to get promotion thresholds
|
|
3098
|
-
metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
|
|
3099
|
-
|
|
3100
|
-
# Get threshold for this type (default: philosophy=5, pattern=3, redirect=2, stack=1, decree=0)
|
|
3101
|
-
threshold=$(echo "$metadata" | jq -r ".promotion_thresholds.${wisdom_type} // null")
|
|
3102
|
-
if [[ "$threshold" == "null" ]]; then
|
|
3103
|
-
case "$wisdom_type" in
|
|
3104
|
-
philosophy) threshold=5 ;;
|
|
3105
|
-
pattern) threshold=3 ;;
|
|
3106
|
-
redirect) threshold=2 ;;
|
|
3107
|
-
stack) threshold=1 ;;
|
|
3108
|
-
decree) threshold=0 ;;
|
|
3109
|
-
*) threshold=1 ;;
|
|
3110
|
-
esac
|
|
3111
|
-
fi
|
|
3112
|
-
|
|
3113
|
-
# For decrees, always promote immediately (threshold 0)
|
|
3114
|
-
# For other types, we assume validation count is passed or threshold is met
|
|
3115
|
-
# In a real implementation, this would check a validation counter
|
|
3116
|
-
# For now, we append if threshold allows (decrees always, others need external validation)
|
|
3117
|
-
|
|
3118
|
-
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
3119
|
-
|
|
3120
|
-
# Map type to section header and emoji
|
|
3121
|
-
case "$wisdom_type" in
|
|
3122
|
-
philosophy) section_header="## 📜 Philosophies" ;;
|
|
3123
|
-
pattern) section_header="## 🧭 Patterns" ;;
|
|
3124
|
-
redirect) section_header="## ⚠️ Redirects" ;;
|
|
3125
|
-
stack) section_header="## 🔧 Stack Wisdom" ;;
|
|
3126
|
-
decree) section_header="## 🏛️ Decrees" ;;
|
|
3127
|
-
esac
|
|
3128
|
-
|
|
3129
|
-
# Build the new entry
|
|
3130
|
-
entry="- **${colony_name}** (${ts}): ${content}"
|
|
3131
|
-
|
|
3132
|
-
# Create temp file for atomic write
|
|
3133
|
-
tmp_file="${queen_file}.tmp.$$"
|
|
3134
|
-
|
|
3135
|
-
# Find line numbers for section boundaries
|
|
3136
|
-
section_line=$(grep -n "^${section_header}$" "$queen_file" | head -1 | cut -d: -f1)
|
|
3137
|
-
next_section_line=$(tail -n +$((section_line + 1)) "$queen_file" | grep -n "^## " | head -1 | cut -d: -f1)
|
|
3138
|
-
if [[ -n "$next_section_line" ]]; then
|
|
3139
|
-
section_end=$((section_line + next_section_line - 1))
|
|
3140
|
-
else
|
|
3141
|
-
section_end=$(wc -l < "$queen_file")
|
|
3142
|
-
fi
|
|
3143
|
-
|
|
3144
|
-
# Check if section has placeholder (grep returns 1 when no matches, handle with || true)
|
|
3145
|
-
has_placeholder=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -c "No.*recorded yet" || true)
|
|
3146
|
-
has_placeholder=${has_placeholder:-0}
|
|
3147
|
-
|
|
3148
|
-
if [[ "$has_placeholder" -gt 0 ]]; then
|
|
3149
|
-
# Replace placeholder with entry - only within the target section
|
|
3150
|
-
# Find the specific line number of the placeholder within the section
|
|
3151
|
-
placeholder_line=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -n "^\\*No .* recorded yet" | head -1 | cut -d: -f1)
|
|
3152
|
-
if [[ -n "$placeholder_line" ]]; then
|
|
3153
|
-
actual_line=$((section_line + placeholder_line - 1))
|
|
3154
|
-
sed "${actual_line}c\\
|
|
3155
|
-
${entry}" "$queen_file" > "$tmp_file"
|
|
3156
|
-
else
|
|
3157
|
-
# Fallback: insert after section header
|
|
3158
|
-
sed "${section_line}a\\
|
|
3159
|
-
${entry}" "$queen_file" > "$tmp_file"
|
|
3160
|
-
fi
|
|
3161
|
-
else
|
|
3162
|
-
# Insert entry after the description paragraph (after the second empty line in section)
|
|
3163
|
-
# The structure is: header, blank, description, blank, [entries...]
|
|
3164
|
-
# We want to insert after the blank line following the description
|
|
3165
|
-
empty_lines=$(sed -n "$((section_line + 1)),${section_end}p" "$queen_file" | grep -n "^$" | cut -d: -f1)
|
|
3166
|
-
# Get the second empty line (after description)
|
|
3167
|
-
insert_line=$(echo "$empty_lines" | sed -n '2p')
|
|
3168
|
-
if [[ -n "$insert_line" ]]; then
|
|
3169
|
-
insert_line=$((section_line + insert_line))
|
|
3170
|
-
else
|
|
3171
|
-
# Fallback: use first empty line
|
|
3172
|
-
insert_line=$(echo "$empty_lines" | head -1)
|
|
3173
|
-
if [[ -n "$insert_line" ]]; then
|
|
3174
|
-
insert_line=$((section_line + insert_line))
|
|
3175
|
-
else
|
|
3176
|
-
insert_line=$((section_line + 1))
|
|
3177
|
-
fi
|
|
3178
|
-
fi
|
|
3179
|
-
# Insert the entry after the found line
|
|
3180
|
-
sed "${insert_line}a\\
|
|
3181
|
-
${entry}" "$queen_file" > "$tmp_file"
|
|
3182
|
-
fi
|
|
3183
|
-
|
|
3184
|
-
# Update Evolution Log in temp file
|
|
3185
|
-
ev_entry="| ${ts} | ${colony_name} | promoted_${wisdom_type} | Added: ${content:0:50}... |"
|
|
3186
|
-
# Find the line after the separator in Evolution Log table
|
|
3187
|
-
ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1)
|
|
3188
|
-
|
|
3189
|
-
# Use awk for cross-platform insertion
|
|
3190
|
-
awk -v line="$ev_separator" -v entry="$ev_entry" 'NR==line{print; print entry; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
|
|
3191
|
-
|
|
3192
|
-
# Update METADATA stats in temp file
|
|
3193
|
-
# Map wisdom_type to stat key (irregular plurals handled)
|
|
3194
|
-
case "$wisdom_type" in
|
|
3195
|
-
stack) stat_key="total_stack_entries" ;;
|
|
3196
|
-
philosophy) stat_key="total_philosophies" ;;
|
|
3197
|
-
*) stat_key="total_${wisdom_type}s" ;;
|
|
3198
|
-
esac
|
|
3199
|
-
# Read current count from temp file (which has the latest state)
|
|
3200
|
-
current_count=$(grep "\"${stat_key}\":" "$tmp_file" 2>/dev/null | grep -o '[0-9]*' | head -1 || true)
|
|
3201
|
-
current_count=${current_count:-0}
|
|
3202
|
-
new_count=$((current_count + 1))
|
|
3203
|
-
|
|
3204
|
-
# Update last_evolved using awk
|
|
3205
|
-
awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
|
|
3206
|
-
|
|
3207
|
-
# Update stats count using awk
|
|
3208
|
-
awk -v type="$stat_key" -v count="$new_count" '{
|
|
3209
|
-
gsub("\"" type "\": [0-9]*", "\"" type "\": " count)
|
|
3210
|
-
print
|
|
3211
|
-
}' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
|
|
3212
|
-
|
|
3213
|
-
# Add colony to colonies_contributed if not present
|
|
3214
|
-
if ! grep -q "\"${colony_name}\"" "$tmp_file"; then
|
|
3215
|
-
# Add to colonies_contributed array using awk - handle empty and non-empty arrays
|
|
3216
|
-
awk -v colony="$colony_name" '
|
|
3217
|
-
/"colonies_contributed": \[\]/ {
|
|
3218
|
-
gsub(/"colonies_contributed": \[\]/, "\"colonies_contributed\": [\"" colony "\"]")
|
|
3219
|
-
print
|
|
3220
|
-
next
|
|
3221
|
-
}
|
|
3222
|
-
/"colonies_contributed": \[/ && !/\]/ {
|
|
3223
|
-
# Multi-line array, add at next closing bracket
|
|
3224
|
-
print
|
|
3225
|
-
next
|
|
3226
|
-
}
|
|
3227
|
-
/"colonies_contributed": \[/ {
|
|
3228
|
-
# Single-line array with elements
|
|
3229
|
-
gsub(/\]$/, "\"" colony "\", ]")
|
|
3230
|
-
print
|
|
3231
|
-
next
|
|
3232
|
-
}
|
|
3233
|
-
{ print }
|
|
3234
|
-
' "$tmp_file" > "${tmp_file}.col" && mv "${tmp_file}.col" "$tmp_file"
|
|
3235
|
-
fi
|
|
3236
|
-
|
|
3237
|
-
# Atomic move
|
|
3238
|
-
mv "$tmp_file" "$queen_file"
|
|
3239
|
-
|
|
3240
|
-
json_ok "{\"promoted\":true,\"type\":\"$wisdom_type\",\"colony\":\"$colony_name\",\"timestamp\":\"$ts\",\"threshold\":$threshold,\"new_count\":$new_count}"
|
|
3241
|
-
;;
|
|
3242
|
-
|
|
3243
|
-
survey-load)
|
|
3244
|
-
phase_type="${1:-}"
|
|
3245
|
-
survey_dir=".aether/data/survey"
|
|
3246
|
-
|
|
3247
|
-
if [[ ! -d "$survey_dir" ]]; then
|
|
3248
|
-
json_err "$E_FILE_NOT_FOUND" "No survey found"
|
|
3249
|
-
fi
|
|
3250
|
-
|
|
3251
|
-
docs=""
|
|
3252
|
-
case "$phase_type" in
|
|
3253
|
-
*frontend*|*component*|*UI*|*page*|*button*)
|
|
3254
|
-
docs="DISCIPLINES.md,CHAMBERS.md"
|
|
3255
|
-
;;
|
|
3256
|
-
*API*|*endpoint*|*backend*|*route*)
|
|
3257
|
-
docs="BLUEPRINT.md,DISCIPLINES.md"
|
|
3258
|
-
;;
|
|
3259
|
-
*database*|*schema*|*model*|*migration*)
|
|
3260
|
-
docs="BLUEPRINT.md,PROVISIONS.md"
|
|
3261
|
-
;;
|
|
3262
|
-
*test*|*spec*|*coverage*)
|
|
3263
|
-
docs="SENTINEL-PROTOCOLS.md,DISCIPLINES.md"
|
|
3264
|
-
;;
|
|
3265
|
-
*integration*|*external*|*client*)
|
|
3266
|
-
docs="TRAILS.md,PROVISIONS.md"
|
|
3267
|
-
;;
|
|
3268
|
-
*refactor*|*cleanup*|*debt*)
|
|
3269
|
-
docs="PATHOGENS.md,BLUEPRINT.md"
|
|
3270
|
-
;;
|
|
3271
|
-
*setup*|*config*|*initialize*)
|
|
3272
|
-
docs="PROVISIONS.md,CHAMBERS.md"
|
|
3273
|
-
;;
|
|
3274
|
-
*)
|
|
3275
|
-
docs="PROVISIONS.md,BLUEPRINT.md"
|
|
3276
|
-
;;
|
|
3277
|
-
esac
|
|
3278
|
-
|
|
3279
|
-
json_ok "{\"ok\":true,\"docs\":\"$docs\",\"dir\":\"$survey_dir\"}"
|
|
3280
|
-
;;
|
|
3281
|
-
|
|
3282
|
-
survey-verify)
|
|
3283
|
-
survey_dir=".aether/data/survey"
|
|
3284
|
-
required="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
|
|
3285
|
-
missing=""
|
|
3286
|
-
counts=""
|
|
3287
|
-
|
|
3288
|
-
for doc in $required; do
|
|
3289
|
-
if [[ ! -f "$survey_dir/$doc" ]]; then
|
|
3290
|
-
missing="$missing $doc"
|
|
3291
|
-
else
|
|
3292
|
-
lines=$(wc -l < "$survey_dir/$doc" | tr -d ' ')
|
|
3293
|
-
counts="$counts $doc:$lines"
|
|
3294
|
-
fi
|
|
3295
|
-
done
|
|
3296
|
-
|
|
3297
|
-
if [[ -n "$missing" ]]; then
|
|
3298
|
-
json_err "$E_FILE_NOT_FOUND" "Missing survey documents" "{\"missing\":\"$missing\"}"
|
|
3299
|
-
fi
|
|
3300
|
-
|
|
3301
|
-
json_ok "{\"ok\":true,\"counts\":\"$counts\"}"
|
|
3302
|
-
;;
|
|
3303
|
-
|
|
3304
|
-
checkpoint-check)
|
|
3305
|
-
allowlist_file="$DATA_DIR/checkpoint-allowlist.json"
|
|
3306
|
-
|
|
3307
|
-
if [[ ! -f "$allowlist_file" ]]; then
|
|
3308
|
-
json_err "$E_FILE_NOT_FOUND" "Allowlist not found" "{\"path\":\"$allowlist_file\"}"
|
|
3309
|
-
fi
|
|
3310
|
-
|
|
3311
|
-
# Get dirty files from git (staged or unstaged)
|
|
3312
|
-
dirty_files=$(git status --porcelain 2>/dev/null | awk '{print $2}' || true)
|
|
3313
|
-
|
|
3314
|
-
if [[ -z "$dirty_files" ]]; then
|
|
3315
|
-
json_ok '{"ok":true,"system_files":[],"user_files":[],"has_user_files":false}'
|
|
3316
|
-
exit 0
|
|
3317
|
-
fi
|
|
3318
|
-
|
|
3319
|
-
# Temporary files for building JSON
|
|
3320
|
-
system_files_tmp=$(mktemp)
|
|
3321
|
-
user_files_tmp=$(mktemp)
|
|
3322
|
-
|
|
3323
|
-
# Check each file against allowlist patterns
|
|
3324
|
-
for file in $dirty_files; do
|
|
3325
|
-
is_system=false
|
|
3326
|
-
|
|
3327
|
-
# Check against system file patterns
|
|
3328
|
-
if [[ "$file" == ".aether/aether-utils.sh" ]]; then
|
|
3329
|
-
is_system=true
|
|
3330
|
-
elif [[ "$file" == ".aether/workers.md" ]]; then
|
|
3331
|
-
is_system=true
|
|
3332
|
-
elif [[ "$file" == .aether/docs/*.md ]]; then
|
|
3333
|
-
is_system=true
|
|
3334
|
-
elif [[ "$file" == .claude/commands/ant/*.md ]] || [[ "$file" == .claude/commands/ant/**/*.md ]]; then
|
|
3335
|
-
is_system=true
|
|
3336
|
-
elif [[ "$file" == .claude/commands/st/*.md ]] || [[ "$file" == .claude/commands/st/**/*.md ]]; then
|
|
3337
|
-
is_system=true
|
|
3338
|
-
elif [[ "$file" == .opencode/commands/ant/*.md ]] || [[ "$file" == .opencode/commands/ant/**/*.md ]]; then
|
|
3339
|
-
is_system=true
|
|
3340
|
-
elif [[ "$file" == .opencode/agents/*.md ]] || [[ "$file" == .opencode/agents/**/*.md ]]; then
|
|
3341
|
-
is_system=true
|
|
3342
|
-
elif [[ "$file" == runtime/* ]]; then
|
|
3343
|
-
is_system=true
|
|
3344
|
-
elif [[ "$file" == bin/* ]]; then
|
|
3345
|
-
is_system=true
|
|
3346
|
-
fi
|
|
3347
|
-
|
|
3348
|
-
if [[ "$is_system" == "true" ]]; then
|
|
3349
|
-
echo "$file" >> "$system_files_tmp"
|
|
3350
|
-
else
|
|
3351
|
-
echo "$file" >> "$user_files_tmp"
|
|
3352
|
-
fi
|
|
3353
|
-
done
|
|
3354
|
-
|
|
3355
|
-
# Build JSON using jq if available, otherwise use simple format
|
|
3356
|
-
if command -v jq >/dev/null 2>&1; then
|
|
3357
|
-
result=$(jq -n \
|
|
3358
|
-
--argjson system "$(jq -R . < "$system_files_tmp" 2>/dev/null | jq -s .)" \
|
|
3359
|
-
--argjson user "$(jq -R . < "$user_files_tmp" 2>/dev/null | jq -s .)" \
|
|
3360
|
-
'{ok: true, system_files: $system, user_files: $user, has_user_files: ($user | length > 0)}')
|
|
3361
|
-
else
|
|
3362
|
-
# Fallback without jq - simple output
|
|
3363
|
-
system_count=$(wc -l < "$system_files_tmp" 2>/dev/null | tr -d ' ' || echo "0")
|
|
3364
|
-
user_count=$(wc -l < "$user_files_tmp" 2>/dev/null | tr -d ' ' || echo "0")
|
|
3365
|
-
has_user=false
|
|
3366
|
-
[[ "$user_count" -gt 0 ]] && has_user=true
|
|
3367
|
-
result="{\"ok\":true,\"system_files\":[],\"user_files\":[],\"has_user_files\":$has_user}"
|
|
3368
|
-
fi
|
|
3369
|
-
|
|
3370
|
-
rm -f "$system_files_tmp" "$user_files_tmp"
|
|
3371
|
-
echo "$result"
|
|
3372
|
-
exit 0
|
|
3373
|
-
;;
|
|
3374
|
-
|
|
3375
|
-
normalize-args)
|
|
3376
|
-
# Normalize arguments from Claude Code ($ARGUMENTS) or OpenCode ($@)
|
|
3377
|
-
# Usage: bash .aether/aether-utils.sh normalize-args [args...]
|
|
3378
|
-
# Or: eval "$(bash .aether/aether-utils.sh normalize-args)"
|
|
3379
|
-
#
|
|
3380
|
-
# Claude Code passes args in $ARGUMENTS variable
|
|
3381
|
-
# OpenCode passes args in $@ (positional parameters)
|
|
3382
|
-
# This command outputs the normalized arguments as a single string
|
|
3383
|
-
|
|
3384
|
-
normalized=""
|
|
3385
|
-
|
|
3386
|
-
# Try Claude Code style first ($ARGUMENTS environment variable)
|
|
3387
|
-
if [ -n "${ARGUMENTS:-}" ]; then
|
|
3388
|
-
normalized="$ARGUMENTS"
|
|
3389
|
-
# Fall back to OpenCode style ($@ positional params)
|
|
3390
|
-
elif [ $# -gt 0 ]; then
|
|
3391
|
-
# Preserve arguments with spaces by quoting
|
|
3392
|
-
for arg in "$@"; do
|
|
3393
|
-
if [[ "$arg" == *" "* ]] || [[ "$arg" == *"\t"* ]] || [[ "$arg" == *"\n"* ]]; then
|
|
3394
|
-
# Quote arguments containing whitespace
|
|
3395
|
-
normalized="$normalized \"$arg\""
|
|
3396
|
-
else
|
|
3397
|
-
normalized="$normalized $arg"
|
|
3398
|
-
fi
|
|
3399
|
-
done
|
|
3400
|
-
# Trim leading space
|
|
3401
|
-
normalized="${normalized# }"
|
|
3402
|
-
fi
|
|
3403
|
-
|
|
3404
|
-
# Output normalized arguments
|
|
3405
|
-
echo "$normalized"
|
|
3406
|
-
exit 0
|
|
3407
|
-
;;
|
|
3408
|
-
|
|
3409
|
-
# Backward compatibility wrappers for session commands
|
|
3410
|
-
survey-verify-fresh)
|
|
3411
|
-
# Backward compatibility: delegate to session-verify-fresh --command survey
|
|
3412
|
-
# Usage: bash .aether/aether-utils.sh survey-verify-fresh [--force] <survey_start_unixtime>
|
|
3413
|
-
|
|
3414
|
-
force_mode=""
|
|
3415
|
-
survey_start_time=""
|
|
3416
|
-
|
|
3417
|
-
# Parse arguments
|
|
3418
|
-
for arg in "$@"; do
|
|
3419
|
-
if [[ "$arg" == "--force" ]]; then
|
|
3420
|
-
force_mode="--force"
|
|
3421
|
-
elif [[ "$arg" =~ ^[0-9]+$ ]]; then
|
|
3422
|
-
survey_start_time="$arg"
|
|
3423
|
-
fi
|
|
3424
|
-
done
|
|
3425
|
-
|
|
3426
|
-
# Delegate to generic command
|
|
3427
|
-
if [[ -n "$force_mode" ]]; then
|
|
3428
|
-
$0 session-verify-fresh --command survey --force "$survey_start_time"
|
|
3429
|
-
else
|
|
3430
|
-
$0 session-verify-fresh --command survey "$survey_start_time"
|
|
3431
|
-
fi
|
|
3432
|
-
;;
|
|
3433
|
-
|
|
3434
|
-
survey-clear)
|
|
3435
|
-
# Backward compatibility: delegate to session-clear --command survey
|
|
3436
|
-
# Usage: bash .aether/aether-utils.sh survey-clear [--dry-run]
|
|
3437
|
-
|
|
3438
|
-
dry_run=""
|
|
3439
|
-
|
|
3440
|
-
# Parse arguments
|
|
3441
|
-
for arg in "$@"; do
|
|
3442
|
-
if [[ "$arg" == "--dry-run" ]]; then
|
|
3443
|
-
dry_run="--dry-run"
|
|
3444
|
-
fi
|
|
3445
|
-
done
|
|
3446
|
-
|
|
3447
|
-
# Delegate to generic command
|
|
3448
|
-
if [[ "$dry_run" == "--dry-run" ]]; then
|
|
3449
|
-
$0 session-clear --command survey --dry-run
|
|
3450
|
-
else
|
|
3451
|
-
$0 session-clear --command survey
|
|
3452
|
-
fi
|
|
3453
|
-
;;
|
|
3454
|
-
|
|
3455
|
-
session-verify-fresh)
|
|
3456
|
-
# Generic session freshness verification
|
|
3457
|
-
# Usage: bash .aether/aether-utils.sh session-verify-fresh --command <name> [--force] <session_start_unixtime>
|
|
3458
|
-
# Returns: JSON with pass/fail status and file details
|
|
3459
|
-
|
|
3460
|
-
# Parse arguments
|
|
3461
|
-
command_name=""
|
|
3462
|
-
force_mode=""
|
|
3463
|
-
session_start_time=""
|
|
3464
|
-
|
|
3465
|
-
while [[ $# -gt 0 ]]; do
|
|
3466
|
-
case "$1" in
|
|
3467
|
-
--command) command_name="$2"; shift 2 ;;
|
|
3468
|
-
--force) force_mode="--force"; shift ;;
|
|
3469
|
-
*) session_start_time="$1"; shift ;;
|
|
3470
|
-
esac
|
|
3471
|
-
done
|
|
3472
|
-
|
|
3473
|
-
# Validate command name
|
|
3474
|
-
[[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-verify-fresh --command <name> [--force] <session_start>"
|
|
3475
|
-
|
|
3476
|
-
# Map command to directory and files (using env var override pattern)
|
|
3477
|
-
case "$command_name" in
|
|
3478
|
-
survey)
|
|
3479
|
-
session_dir="${SURVEY_DIR:-.aether/data/survey}"
|
|
3480
|
-
required_docs="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
|
|
3481
|
-
;;
|
|
3482
|
-
oracle)
|
|
3483
|
-
session_dir="${ORACLE_DIR:-.aether/oracle}"
|
|
3484
|
-
required_docs="progress.md research.json"
|
|
3485
|
-
;;
|
|
3486
|
-
watch)
|
|
3487
|
-
session_dir="${WATCH_DIR:-.aether/data}"
|
|
3488
|
-
required_docs="watch-status.txt watch-progress.txt"
|
|
3489
|
-
;;
|
|
3490
|
-
swarm)
|
|
3491
|
-
session_dir="${SWARM_DIR:-.aether/data/swarm}"
|
|
3492
|
-
required_docs="findings.json"
|
|
3493
|
-
;;
|
|
3494
|
-
init)
|
|
3495
|
-
session_dir="${INIT_DIR:-.aether/data}"
|
|
3496
|
-
required_docs="COLONY_STATE.json constraints.json"
|
|
3497
|
-
;;
|
|
3498
|
-
seal|entomb)
|
|
3499
|
-
session_dir="${ARCHIVE_DIR:-.aether/data/archive}"
|
|
3500
|
-
required_docs="manifest.json"
|
|
3501
|
-
;;
|
|
3502
|
-
*)
|
|
3503
|
-
json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name" '{"commands":["survey","oracle","watch","swarm","init","seal","entomb"]}'
|
|
3504
|
-
;;
|
|
3505
|
-
esac
|
|
3506
|
-
|
|
3507
|
-
# Initialize result arrays
|
|
3508
|
-
fresh_docs=""
|
|
3509
|
-
stale_docs=""
|
|
3510
|
-
missing_docs=""
|
|
3511
|
-
total_lines=0
|
|
3512
|
-
|
|
3513
|
-
for doc in $required_docs; do
|
|
3514
|
-
doc_path="$session_dir/$doc"
|
|
3515
|
-
|
|
3516
|
-
if [[ ! -f "$doc_path" ]]; then
|
|
3517
|
-
missing_docs="${missing_docs:+$missing_docs }$doc"
|
|
3518
|
-
continue
|
|
3519
|
-
fi
|
|
3520
|
-
|
|
3521
|
-
# Get line count
|
|
3522
|
-
lines=$(wc -l < "$doc_path" 2>/dev/null | tr -d ' ' || echo "0")
|
|
3523
|
-
total_lines=$((total_lines + lines))
|
|
3524
|
-
|
|
3525
|
-
# In force mode, accept any existing file
|
|
3526
|
-
if [[ "$force_mode" == "--force" ]]; then
|
|
3527
|
-
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
3528
|
-
continue
|
|
3529
|
-
fi
|
|
3530
|
-
|
|
3531
|
-
# Check timestamp if session_start_time provided
|
|
3532
|
-
if [[ -n "$session_start_time" ]]; then
|
|
3533
|
-
# Cross-platform stat: macOS uses -f %m, Linux uses -c %Y
|
|
3534
|
-
file_mtime=$(stat -f %m "$doc_path" 2>/dev/null || stat -c %Y "$doc_path" 2>/dev/null || echo "0")
|
|
3535
|
-
|
|
3536
|
-
if [[ "$file_mtime" -ge "$session_start_time" ]]; then
|
|
3537
|
-
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
3538
|
-
else
|
|
3539
|
-
stale_docs="${stale_docs:+$stale_docs }$doc"
|
|
3540
|
-
fi
|
|
3541
|
-
else
|
|
3542
|
-
# No start time provided - accept existing file (backward compatible)
|
|
3543
|
-
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
3544
|
-
fi
|
|
3545
|
-
done
|
|
3546
|
-
|
|
3547
|
-
# Determine pass/fail
|
|
3548
|
-
# pass = true if: no stale files (fresh files can coexist with missing files)
|
|
3549
|
-
# missing files are ok - they will be created during the session
|
|
3550
|
-
pass=false
|
|
3551
|
-
if [[ "$force_mode" == "--force" ]] || [[ -z "$stale_docs" ]]; then
|
|
3552
|
-
pass=true
|
|
3553
|
-
fi
|
|
3554
|
-
|
|
3555
|
-
# Build JSON response
|
|
3556
|
-
fresh_json=""
|
|
3557
|
-
for item in $fresh_docs; do fresh_json="$fresh_json\"$item\","; done
|
|
3558
|
-
fresh_json="[${fresh_json%,}]"
|
|
3559
|
-
|
|
3560
|
-
stale_json=""
|
|
3561
|
-
for item in $stale_docs; do stale_json="$stale_json\"$item\","; done
|
|
3562
|
-
stale_json="[${stale_json%,}]"
|
|
3563
|
-
|
|
3564
|
-
missing_json=""
|
|
3565
|
-
for item in $missing_docs; do missing_json="$missing_json\"$item\","; done
|
|
3566
|
-
missing_json="[${missing_json%,}]"
|
|
3567
|
-
|
|
3568
|
-
echo "{\"ok\":$pass,\"command\":\"$command_name\",\"fresh\":$fresh_json,\"stale\":$stale_json,\"missing\":$missing_json,\"total_lines\":$total_lines}"
|
|
3569
|
-
exit 0
|
|
3570
|
-
;;
|
|
3571
|
-
|
|
3572
|
-
session-clear)
|
|
3573
|
-
# Generic session file clearing
|
|
3574
|
-
# Usage: bash .aether/aether-utils.sh session-clear --command <name> [--dry-run]
|
|
3575
|
-
|
|
3576
|
-
# Parse arguments
|
|
3577
|
-
command_name=""
|
|
3578
|
-
dry_run=""
|
|
3579
|
-
|
|
3580
|
-
while [[ $# -gt 0 ]]; do
|
|
3581
|
-
case "$1" in
|
|
3582
|
-
--command) command_name="$2"; shift 2 ;;
|
|
3583
|
-
--dry-run) dry_run="--dry-run"; shift ;;
|
|
3584
|
-
*) shift ;;
|
|
3585
|
-
esac
|
|
3586
|
-
done
|
|
3587
|
-
|
|
3588
|
-
[[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-clear --command <name> [--dry-run]"
|
|
3589
|
-
|
|
3590
|
-
# Map command to directory and files
|
|
3591
|
-
case "$command_name" in
|
|
3592
|
-
survey)
|
|
3593
|
-
session_dir="${SURVEY_DIR:-.aether/data/survey}"
|
|
3594
|
-
files="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
|
|
3595
|
-
;;
|
|
3596
|
-
oracle)
|
|
3597
|
-
session_dir="${ORACLE_DIR:-.aether/oracle}"
|
|
3598
|
-
files="progress.md research.json .stop"
|
|
3599
|
-
# Also clear discoveries subdirectory
|
|
3600
|
-
subdir_files="discoveries/*"
|
|
3601
|
-
;;
|
|
3602
|
-
watch)
|
|
3603
|
-
session_dir="${WATCH_DIR:-.aether/data}"
|
|
3604
|
-
files="watch-status.txt watch-progress.txt"
|
|
3605
|
-
;;
|
|
3606
|
-
swarm)
|
|
3607
|
-
session_dir="${SWARM_DIR:-.aether/data/swarm}"
|
|
3608
|
-
files="findings.json display.json timing.json"
|
|
3609
|
-
;;
|
|
3610
|
-
init)
|
|
3611
|
-
# Init clear is destructive - blocked for auto-clear
|
|
3612
|
-
json_err "$E_VALIDATION_FAILED" "Command 'init' is protected and cannot be auto-cleared. Use manual removal of COLONY_STATE.json if absolutely necessary."
|
|
3613
|
-
;;
|
|
3614
|
-
seal|entomb)
|
|
3615
|
-
# Archive operations should never be auto-cleared
|
|
3616
|
-
json_err "$E_VALIDATION_FAILED" "Command '$command_name' is protected and cannot be auto-cleared. Archives and chambers must be managed manually."
|
|
3617
|
-
;;
|
|
3618
|
-
*)
|
|
3619
|
-
json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name"
|
|
3620
|
-
;;
|
|
3621
|
-
esac
|
|
3622
|
-
|
|
3623
|
-
cleared=""
|
|
3624
|
-
errors=""
|
|
3625
|
-
|
|
3626
|
-
if [[ -d "$session_dir" && -n "$files" ]]; then
|
|
3627
|
-
for doc in $files; do
|
|
3628
|
-
doc_path="$session_dir/$doc"
|
|
3629
|
-
if [[ -f "$doc_path" ]]; then
|
|
3630
|
-
if [[ "$dry_run" == "--dry-run" ]]; then
|
|
3631
|
-
cleared="$cleared $doc"
|
|
3632
|
-
else
|
|
3633
|
-
if rm -f "$doc_path" 2>/dev/null; then
|
|
3634
|
-
cleared="$cleared $doc"
|
|
3635
|
-
else
|
|
3636
|
-
errors="$errors $doc"
|
|
3637
|
-
fi
|
|
3638
|
-
fi
|
|
3639
|
-
fi
|
|
3640
|
-
done
|
|
3641
|
-
|
|
3642
|
-
# Handle oracle discoveries subdirectory
|
|
3643
|
-
if [[ "$command_name" == "oracle" && -d "$session_dir/discoveries" ]]; then
|
|
3644
|
-
if [[ "$dry_run" == "--dry-run" ]]; then
|
|
3645
|
-
cleared="$cleared discoveries/"
|
|
3646
|
-
else
|
|
3647
|
-
rm -rf "$session_dir/discoveries" 2>/dev/null && cleared="$cleared discoveries/" || errors="$errors discoveries/"
|
|
3648
|
-
fi
|
|
3649
|
-
fi
|
|
3650
|
-
fi
|
|
3651
|
-
|
|
3652
|
-
json_ok "{\"command\":\"$command_name\",\"cleared\":\"${cleared// /}\",\"errors\":\"${errors// /}\",\"dry_run\":$([[ "$dry_run" == "--dry-run" ]] && echo "true" || echo "false")}"
|
|
3653
|
-
;;
|
|
3654
|
-
|
|
3655
|
-
pheromone-export)
|
|
3656
|
-
# Export pheromones to eternal XML format
|
|
3657
|
-
# Usage: pheromone-export [input_json] [output_xml]
|
|
3658
|
-
# input_json: Path to pheromones.json (default: .aether/data/pheromones.json)
|
|
3659
|
-
# output_xml: Path to output XML (default: ~/.aether/eternal/pheromones.xml)
|
|
3660
|
-
|
|
3661
|
-
input_json="${1:-.aether/data/pheromones.json}"
|
|
3662
|
-
output_xml="${2:-$HOME/.aether/eternal/pheromones.xml}"
|
|
3663
|
-
schema_file="${3:-$SCRIPT_DIR/schemas/pheromone.xsd}"
|
|
3664
|
-
|
|
3665
|
-
# Ensure xml-utils.sh is sourced
|
|
3666
|
-
if ! type pheromone-export &>/dev/null; then
|
|
3667
|
-
[[ -f "$SCRIPT_DIR/utils/xml-utils.sh" ]] && source "$SCRIPT_DIR/utils/xml-utils.sh"
|
|
3668
|
-
fi
|
|
3669
|
-
|
|
3670
|
-
if type pheromone-export &>/dev/null; then
|
|
3671
|
-
pheromone-export "$input_json" "$output_xml" "$schema_file"
|
|
3672
|
-
else
|
|
3673
|
-
json_err "$E_DEPENDENCY_MISSING" "xml-utils.sh not available for pheromone export"
|
|
3674
|
-
fi
|
|
3675
|
-
;;
|
|
3676
|
-
|
|
3677
|
-
# ============================================================================
|
|
3678
|
-
# Session Continuity Commands
|
|
3679
|
-
# ============================================================================
|
|
3680
|
-
|
|
3681
|
-
session-init)
|
|
3682
|
-
# Initialize a new session tracking file
|
|
3683
|
-
# Usage: session-init [session_id] [goal]
|
|
3684
|
-
session_id="${2:-$(date +%s)_$(openssl rand -hex 4 2>/dev/null || echo $$)}"
|
|
3685
|
-
goal="${3:-}"
|
|
3686
|
-
|
|
3687
|
-
session_file="$DATA_DIR/session.json"
|
|
3688
|
-
|
|
3689
|
-
cat > "$session_file" << EOF
|
|
3690
|
-
{
|
|
3691
|
-
"session_id": "$session_id",
|
|
3692
|
-
"started_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
|
3693
|
-
"last_command": null,
|
|
3694
|
-
"last_command_at": null,
|
|
3695
|
-
"colony_goal": "$goal",
|
|
3696
|
-
"current_phase": 0,
|
|
3697
|
-
"current_milestone": "First Mound",
|
|
3698
|
-
"suggested_next": "/ant:plan",
|
|
3699
|
-
"context_cleared": false,
|
|
3700
|
-
"resumed_at": null,
|
|
3701
|
-
"active_todos": [],
|
|
3702
|
-
"summary": "Session initialized"
|
|
3703
|
-
}
|
|
3704
|
-
EOF
|
|
3705
|
-
json_ok "{\"session_id\":\"$session_id\",\"goal\":\"$goal\",\"file\":\"$session_file\"}"
|
|
3706
|
-
;;
|
|
3707
|
-
|
|
3708
|
-
session-update)
|
|
3709
|
-
# Update session with latest activity
|
|
3710
|
-
# Usage: session-update <command> [suggested_next] [summary]
|
|
3711
|
-
cmd_run="${2:-}"
|
|
3712
|
-
suggested="${3:-}"
|
|
3713
|
-
summary="${4:-}"
|
|
3714
|
-
|
|
3715
|
-
session_file="$DATA_DIR/session.json"
|
|
3716
|
-
|
|
3717
|
-
if [[ ! -f "$session_file" ]]; then
|
|
3718
|
-
# Auto-initialize if doesn't exist
|
|
3719
|
-
bash "$0" session-init "auto_$(date +%s)" ""
|
|
3720
|
-
fi
|
|
3721
|
-
|
|
3722
|
-
# Read current session
|
|
3723
|
-
current_session=$(cat "$session_file" 2>/dev/null || echo '{}')
|
|
3724
|
-
|
|
3725
|
-
# Extract current values for preservation
|
|
3726
|
-
current_goal=$(echo "$current_session" | jq -r '.colony_goal // empty')
|
|
3727
|
-
current_phase=$(echo "$current_session" | jq -r '.current_phase // 0')
|
|
3728
|
-
current_milestone=$(echo "$current_session" | jq -r '.current_milestone // "First Mound"')
|
|
3729
|
-
|
|
3730
|
-
# Get top 3 TODOs if TO-DOs.md exists
|
|
3731
|
-
todos="[]"
|
|
3732
|
-
if [[ -f "TO-DOs.md" ]]; then
|
|
3733
|
-
todos=$(grep "^### " TO-DOs.md 2>/dev/null | head -3 | sed 's/^### //' | jq -R . | jq -s .)
|
|
3734
|
-
fi
|
|
3735
|
-
|
|
3736
|
-
# Get colony state if exists
|
|
3737
|
-
if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
|
|
3738
|
-
current_goal=$(jq -r '.goal // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_goal")
|
|
3739
|
-
current_phase=$(jq -r '.current_phase // 0' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_phase")
|
|
3740
|
-
current_milestone=$(jq -r '.milestone // "First Mound"' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_milestone")
|
|
3741
|
-
fi
|
|
3742
|
-
|
|
3743
|
-
# Build updated session
|
|
3744
|
-
echo "$current_session" | jq --arg cmd "$cmd_run" \
|
|
3745
|
-
--arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
3746
|
-
--arg suggested "$suggested" \
|
|
3747
|
-
--arg summary "$summary" \
|
|
3748
|
-
--arg goal "$current_goal" \
|
|
3749
|
-
--argjson phase "$current_phase" \
|
|
3750
|
-
--arg milestone "$current_milestone" \
|
|
3751
|
-
--argjson todos "$todos" \
|
|
3752
|
-
'.last_command = $cmd |
|
|
3753
|
-
.last_command_at = $ts |
|
|
3754
|
-
.suggested_next = $suggested |
|
|
3755
|
-
.summary = $summary |
|
|
3756
|
-
.colony_goal = $goal |
|
|
3757
|
-
.current_phase = $phase |
|
|
3758
|
-
.current_milestone = $milestone |
|
|
3759
|
-
.active_todos = $todos' > "$session_file"
|
|
3760
|
-
|
|
3761
|
-
json_ok "{\"updated\":true,\"command\":\"$cmd_run\"}"
|
|
3762
|
-
;;
|
|
3763
|
-
|
|
3764
|
-
session-read)
|
|
3765
|
-
# Read and return current session state
|
|
3766
|
-
session_file="$DATA_DIR/session.json"
|
|
3767
|
-
|
|
3768
|
-
if [[ ! -f "$session_file" ]]; then
|
|
3769
|
-
json_ok "{\"exists\":false,\"session\":null}"
|
|
3770
|
-
exit 0
|
|
3771
|
-
fi
|
|
3772
|
-
|
|
3773
|
-
session_data=$(cat "$session_file" 2>/dev/null || echo '{}')
|
|
3774
|
-
|
|
3775
|
-
# Check if stale (> 24 hours)
|
|
3776
|
-
last_cmd_ts="" is_stale="" age_hours=""
|
|
3777
|
-
last_cmd_ts=$(echo "$session_data" | jq -r '.last_command_at // .started_at // empty')
|
|
3778
|
-
if [[ -n "$last_cmd_ts" ]]; then
|
|
3779
|
-
last_epoch=0 now_epoch=0
|
|
3780
|
-
last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null || echo 0)
|
|
3781
|
-
now_epoch=$(date +%s)
|
|
3782
|
-
age_hours=$(( (now_epoch - last_epoch) / 3600 ))
|
|
3783
|
-
[[ $age_hours -gt 24 ]] && is_stale=true || is_stale=false
|
|
3784
|
-
else
|
|
3785
|
-
is_stale="false"
|
|
3786
|
-
age_hours="unknown"
|
|
3787
|
-
fi
|
|
3788
|
-
|
|
3789
|
-
json_ok "{\"exists\":true,\"is_stale\":$is_stale,\"age_hours\":$age_hours,\"session\":$session_data}"
|
|
3790
|
-
;;
|
|
3791
|
-
|
|
3792
|
-
session-is-stale)
|
|
3793
|
-
# Check if session is stale (returns true/false)
|
|
3794
|
-
session_file="$DATA_DIR/session.json"
|
|
3795
|
-
|
|
3796
|
-
if [[ ! -f "$session_file" ]]; then
|
|
3797
|
-
echo "true"
|
|
3798
|
-
exit 0
|
|
3799
|
-
fi
|
|
3800
|
-
|
|
3801
|
-
last_cmd_ts=$(jq -r '.last_command_at // .started_at // empty' "$session_file" 2>/dev/null)
|
|
3802
|
-
|
|
3803
|
-
if [[ -z "$last_cmd_ts" ]]; then
|
|
3804
|
-
echo "true"
|
|
3805
|
-
exit 0
|
|
3806
|
-
fi
|
|
3807
|
-
|
|
3808
|
-
last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null || echo 0)
|
|
3809
|
-
now_epoch=$(date +%s)
|
|
3810
|
-
age_hours=$(( (now_epoch - last_epoch) / 3600 ))
|
|
3811
|
-
|
|
3812
|
-
[[ $age_hours -gt 24 ]] && echo "true" || echo "false"
|
|
3813
|
-
;;
|
|
3814
|
-
|
|
3815
|
-
session-clear)
|
|
3816
|
-
# Mark session as cleared (preserves file but marks context_cleared)
|
|
3817
|
-
preserve="${2:-false}"
|
|
3818
|
-
session_file="$DATA_DIR/session.json"
|
|
3819
|
-
|
|
3820
|
-
if [[ -f "$session_file" ]]; then
|
|
3821
|
-
if [[ "$preserve" == "true" ]]; then
|
|
3822
|
-
# Just mark as cleared
|
|
3823
|
-
jq '.context_cleared = true' "$session_file" > "$session_file.tmp" && mv "$session_file.tmp" "$session_file"
|
|
3824
|
-
json_ok "{\"cleared\":true,\"preserved\":true}"
|
|
3825
|
-
else
|
|
3826
|
-
# Remove file entirely
|
|
3827
|
-
rm -f "$session_file"
|
|
3828
|
-
json_ok "{\"cleared\":true,\"preserved\":false}"
|
|
3829
|
-
fi
|
|
3830
|
-
else
|
|
3831
|
-
json_ok "{\"cleared\":false,\"reason\":\"no_session_exists\"}"
|
|
3832
|
-
fi
|
|
3833
|
-
;;
|
|
3834
|
-
|
|
3835
|
-
session-mark-resumed)
|
|
3836
|
-
# Mark session as resumed
|
|
3837
|
-
session_file="$DATA_DIR/session.json"
|
|
3838
|
-
|
|
3839
|
-
if [[ -f "$session_file" ]]; then
|
|
3840
|
-
jq --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
3841
|
-
'.resumed_at = $ts | .context_cleared = false' "$session_file" > "$session_file.tmp" && mv "$session_file.tmp" "$session_file"
|
|
3842
|
-
json_ok "{\"resumed\":true,\"timestamp\":\"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"}"
|
|
3843
|
-
else
|
|
3844
|
-
json_err "$E_RESOURCE_NOT_FOUND" "No session to mark as resumed"
|
|
3845
|
-
fi
|
|
3846
|
-
;;
|
|
3847
|
-
|
|
3848
|
-
session-summary)
|
|
3849
|
-
# Get human-readable session summary
|
|
3850
|
-
session_file="$DATA_DIR/session.json"
|
|
3851
|
-
|
|
3852
|
-
if [[ ! -f "$session_file" ]]; then
|
|
3853
|
-
echo "No active session found."
|
|
3854
|
-
exit 0
|
|
3855
|
-
fi
|
|
3856
|
-
|
|
3857
|
-
goal=$(jq -r '.colony_goal // "No goal set"' "$session_file")
|
|
3858
|
-
phase=$(jq -r '.current_phase // 0' "$session_file")
|
|
3859
|
-
milestone=$(jq -r '.current_milestone // "First Mound"' "$session_file")
|
|
3860
|
-
last_cmd=$(jq -r '.last_command // "None"' "$session_file")
|
|
3861
|
-
last_at=$(jq -r '.last_command_at // "Unknown"' "$session_file")
|
|
3862
|
-
suggested=$(jq -r '.suggested_next // "None"' "$session_file")
|
|
3863
|
-
cleared=$(jq -r '.context_cleared // false' "$session_file")
|
|
3864
|
-
|
|
3865
|
-
echo "📋 Session Summary"
|
|
3866
|
-
echo "=================="
|
|
3867
|
-
echo "Goal: $goal"
|
|
3868
|
-
[[ "$phase" != "0" ]] && echo "Phase: $phase"
|
|
3869
|
-
echo "Milestone: $milestone"
|
|
3870
|
-
echo "Last Command: $last_cmd"
|
|
3871
|
-
echo "Last Active: $last_at"
|
|
3872
|
-
[[ "$suggested" != "None" ]] && echo "Suggested Next: $suggested"
|
|
3873
|
-
[[ "$cleared" == "true" ]] && echo "Status: Context was cleared"
|
|
3874
|
-
;;
|
|
3875
|
-
|
|
3876
|
-
*)
|
|
3877
|
-
json_err "$E_VALIDATION_FAILED" "Unknown command: $cmd"
|
|
3878
|
-
;;
|
|
3879
|
-
esac
|