aether-colony 5.0.0 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aether/aether-utils.sh +3226 -3345
- package/.aether/agents-claude/aether-ambassador.md +265 -0
- package/.aether/agents-claude/aether-archaeologist.md +327 -0
- package/.aether/agents-claude/aether-architect.md +236 -0
- package/.aether/agents-claude/aether-auditor.md +271 -0
- package/.aether/agents-claude/aether-builder.md +224 -0
- package/.aether/agents-claude/aether-chaos.md +269 -0
- package/.aether/agents-claude/aether-chronicler.md +305 -0
- package/.aether/agents-claude/aether-gatekeeper.md +330 -0
- package/.aether/agents-claude/aether-includer.md +374 -0
- package/.aether/agents-claude/aether-keeper.md +272 -0
- package/.aether/agents-claude/aether-measurer.md +322 -0
- package/.aether/agents-claude/aether-oracle.md +237 -0
- package/.aether/agents-claude/aether-probe.md +211 -0
- package/.aether/agents-claude/aether-queen.md +330 -0
- package/.aether/agents-claude/aether-route-setter.md +178 -0
- package/.aether/agents-claude/aether-sage.md +418 -0
- package/.aether/agents-claude/aether-scout.md +179 -0
- package/.aether/agents-claude/aether-surveyor-disciplines.md +417 -0
- package/.aether/agents-claude/aether-surveyor-nest.md +355 -0
- package/.aether/agents-claude/aether-surveyor-pathogens.md +289 -0
- package/.aether/agents-claude/aether-surveyor-provisions.md +360 -0
- package/.aether/agents-claude/aether-tracker.md +270 -0
- package/.aether/agents-claude/aether-watcher.md +280 -0
- package/.aether/agents-claude/aether-weaver.md +248 -0
- package/.aether/commands/archaeology.yaml +653 -0
- package/.aether/commands/build.yaml +1221 -0
- package/.aether/commands/chaos.yaml +653 -0
- package/.aether/commands/colonize.yaml +442 -0
- package/.aether/commands/continue.yaml +1484 -0
- package/.aether/commands/council.yaml +509 -0
- package/.aether/commands/data-clean.yaml +80 -0
- package/.aether/commands/dream.yaml +275 -0
- package/.aether/commands/entomb.yaml +863 -0
- package/.aether/commands/export-signals.yaml +64 -0
- package/.aether/commands/feedback.yaml +158 -0
- package/.aether/commands/flag.yaml +160 -0
- package/.aether/commands/flags.yaml +177 -0
- package/.aether/commands/focus.yaml +112 -0
- package/.aether/commands/help.yaml +167 -0
- package/.aether/commands/history.yaml +137 -0
- package/.aether/commands/import-signals.yaml +79 -0
- package/.aether/commands/init.yaml +502 -0
- package/.aether/commands/insert-phase.yaml +102 -0
- package/.aether/commands/interpret.yaml +285 -0
- package/.aether/commands/lay-eggs.yaml +224 -0
- package/.aether/commands/maturity.yaml +122 -0
- package/.aether/commands/memory-details.yaml +74 -0
- package/.aether/commands/migrate-state.yaml +174 -0
- package/.aether/commands/oracle.yaml +1224 -0
- package/.aether/commands/organize.yaml +446 -0
- package/.aether/commands/patrol.yaml +621 -0
- package/.aether/commands/pause-colony.yaml +424 -0
- package/.aether/commands/phase.yaml +124 -0
- package/.aether/commands/pheromones.yaml +153 -0
- package/.aether/commands/plan.yaml +1364 -0
- package/.aether/commands/preferences.yaml +63 -0
- package/.aether/commands/quick.yaml +104 -0
- package/.aether/commands/redirect.yaml +123 -0
- package/.aether/commands/resume-colony.yaml +375 -0
- package/.aether/commands/resume.yaml +407 -0
- package/.aether/commands/run.yaml +229 -0
- package/.aether/commands/seal.yaml +1214 -0
- package/.aether/commands/skill-create.yaml +337 -0
- package/.aether/commands/status.yaml +408 -0
- package/.aether/commands/swarm.yaml +352 -0
- package/.aether/commands/tunnels.yaml +814 -0
- package/.aether/commands/update.yaml +131 -0
- package/.aether/commands/verify-castes.yaml +159 -0
- package/.aether/commands/watch.yaml +454 -0
- package/.aether/docs/INCIDENT_TEMPLATE.md +32 -0
- package/.aether/docs/QUEEN-SYSTEM.md +11 -11
- package/.aether/docs/README.md +32 -2
- package/.aether/docs/command-playbooks/README.md +23 -0
- package/.aether/docs/command-playbooks/build-complete.md +349 -0
- package/.aether/docs/command-playbooks/build-context.md +282 -0
- package/.aether/docs/command-playbooks/build-full.md +1683 -0
- package/.aether/docs/command-playbooks/build-prep.md +284 -0
- package/.aether/docs/command-playbooks/build-verify.md +405 -0
- package/.aether/docs/command-playbooks/build-wave.md +749 -0
- package/.aether/docs/command-playbooks/continue-advance.md +524 -0
- package/.aether/docs/command-playbooks/continue-finalize.md +447 -0
- package/.aether/docs/command-playbooks/continue-full.md +1725 -0
- package/.aether/docs/command-playbooks/continue-gates.md +686 -0
- package/.aether/docs/command-playbooks/continue-verify.md +407 -0
- package/.aether/docs/context-continuity.md +84 -0
- package/.aether/docs/disciplines/DISCIPLINES.md +9 -7
- package/.aether/docs/error-codes.md +1 -1
- package/.aether/docs/known-issues.md +34 -173
- package/.aether/docs/pheromones.md +86 -6
- package/.aether/docs/plans/pheromone-display-plan.md +257 -0
- package/.aether/docs/queen-commands.md +10 -9
- package/.aether/docs/source-of-truth-map.md +132 -0
- package/.aether/docs/xml-utilities.md +47 -0
- package/.aether/rules/aether-colony.md +23 -13
- package/.aether/scripts/incident-test-add.sh +47 -0
- package/.aether/scripts/weekly-audit.sh +79 -0
- package/.aether/skills/.index.json +649 -0
- package/.aether/skills/colony/.manifest.json +16 -0
- package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
- package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
- package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
- package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
- package/.aether/skills/colony/context-management/SKILL.md +80 -0
- package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
- package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
- package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
- package/.aether/skills/colony/state-safety/SKILL.md +84 -0
- package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
- package/.aether/skills/domain/.manifest.json +24 -0
- package/.aether/skills/domain/README.md +33 -0
- package/.aether/skills/domain/django/SKILL.md +49 -0
- package/.aether/skills/domain/docker/SKILL.md +52 -0
- package/.aether/skills/domain/golang/SKILL.md +52 -0
- package/.aether/skills/domain/graphql/SKILL.md +51 -0
- package/.aether/skills/domain/html-css/SKILL.md +48 -0
- package/.aether/skills/domain/nextjs/SKILL.md +45 -0
- package/.aether/skills/domain/nodejs/SKILL.md +53 -0
- package/.aether/skills/domain/postgresql/SKILL.md +53 -0
- package/.aether/skills/domain/prisma/SKILL.md +59 -0
- package/.aether/skills/domain/python/SKILL.md +50 -0
- package/.aether/skills/domain/rails/SKILL.md +52 -0
- package/.aether/skills/domain/react/SKILL.md +45 -0
- package/.aether/skills/domain/rest-api/SKILL.md +58 -0
- package/.aether/skills/domain/svelte/SKILL.md +47 -0
- package/.aether/skills/domain/tailwind/SKILL.md +45 -0
- package/.aether/skills/domain/testing/SKILL.md +53 -0
- package/.aether/skills/domain/typescript/SKILL.md +58 -0
- package/.aether/skills/domain/vue/SKILL.md +49 -0
- package/.aether/templates/QUEEN.md.template +23 -41
- package/.aether/templates/colony-state-reset.jq.template +1 -0
- package/.aether/templates/colony-state.template.json +4 -0
- package/.aether/templates/learning-observations.template.json +6 -0
- package/.aether/templates/midden.template.json +13 -0
- package/.aether/templates/pheromones.template.json +6 -0
- package/.aether/templates/session.template.json +9 -0
- package/.aether/utils/atomic-write.sh +63 -17
- package/.aether/utils/chamber-utils.sh +145 -2
- package/.aether/utils/council.sh +425 -0
- package/.aether/utils/emoji-audit.sh +166 -0
- package/.aether/utils/error-handler.sh +21 -7
- package/.aether/utils/file-lock.sh +182 -27
- package/.aether/utils/flag.sh +278 -0
- package/.aether/utils/hive.sh +572 -0
- package/.aether/utils/immune.sh +508 -0
- package/.aether/utils/learning.sh +1928 -0
- package/.aether/utils/midden.sh +520 -0
- package/.aether/utils/oracle/oracle.md +168 -0
- package/.aether/utils/oracle/oracle.sh +1023 -0
- package/.aether/utils/pheromone.sh +2029 -0
- package/.aether/utils/queen.sh +1710 -0
- package/.aether/utils/scan.sh +860 -0
- package/.aether/utils/semantic-cli.sh +10 -8
- package/.aether/utils/session.sh +816 -0
- package/.aether/utils/skills.sh +509 -0
- package/.aether/utils/spawn-tree.sh +103 -271
- package/.aether/utils/spawn.sh +260 -0
- package/.aether/utils/state-api.sh +389 -0
- package/.aether/utils/state-loader.sh +8 -6
- package/.aether/utils/suggest.sh +611 -0
- package/.aether/utils/swarm-display.sh +10 -1
- package/.aether/utils/swarm.sh +1004 -0
- package/.aether/utils/watch-spawn-tree.sh +11 -2
- package/.aether/utils/xml-compose.sh +2 -2
- package/.aether/utils/xml-convert.sh +9 -5
- package/.aether/utils/xml-core.sh +5 -9
- package/.aether/utils/xml-query.sh +4 -4
- package/.aether/workers.md +86 -67
- package/.claude/agents/ant/aether-ambassador.md +2 -1
- package/.claude/agents/ant/aether-archaeologist.md +6 -1
- package/.claude/agents/ant/aether-architect.md +236 -0
- package/.claude/agents/ant/aether-auditor.md +6 -1
- package/.claude/agents/ant/aether-builder.md +38 -1
- package/.claude/agents/ant/aether-chaos.md +2 -1
- package/.claude/agents/ant/aether-chronicler.md +1 -0
- package/.claude/agents/ant/aether-gatekeeper.md +6 -1
- package/.claude/agents/ant/aether-includer.md +1 -0
- package/.claude/agents/ant/aether-keeper.md +1 -0
- package/.claude/agents/ant/aether-measurer.md +6 -1
- package/.claude/agents/ant/aether-oracle.md +237 -0
- package/.claude/agents/ant/aether-probe.md +2 -1
- package/.claude/agents/ant/aether-queen.md +6 -1
- package/.claude/agents/ant/aether-route-setter.md +6 -1
- package/.claude/agents/ant/aether-sage.md +68 -3
- package/.claude/agents/ant/aether-scout.md +38 -1
- package/.claude/agents/ant/aether-surveyor-disciplines.md +2 -1
- package/.claude/agents/ant/aether-surveyor-nest.md +2 -1
- package/.claude/agents/ant/aether-surveyor-pathogens.md +2 -1
- package/.claude/agents/ant/aether-surveyor-provisions.md +2 -1
- package/.claude/agents/ant/aether-tracker.md +6 -1
- package/.claude/agents/ant/aether-watcher.md +37 -1
- package/.claude/agents/ant/aether-weaver.md +2 -1
- package/.claude/commands/ant/archaeology.md +1 -8
- package/.claude/commands/ant/build.md +43 -1159
- package/.claude/commands/ant/chaos.md +1 -14
- package/.claude/commands/ant/colonize.md +3 -14
- package/.claude/commands/ant/continue.md +40 -1026
- package/.claude/commands/ant/council.md +213 -15
- package/.claude/commands/ant/data-clean.md +81 -0
- package/.claude/commands/ant/dream.md +12 -9
- package/.claude/commands/ant/entomb.md +62 -87
- package/.claude/commands/ant/export-signals.md +57 -0
- package/.claude/commands/ant/feedback.md +18 -0
- package/.claude/commands/ant/flag.md +12 -0
- package/.claude/commands/ant/flags.md +22 -8
- package/.claude/commands/ant/focus.md +18 -0
- package/.claude/commands/ant/help.md +40 -8
- package/.claude/commands/ant/history.md +3 -0
- package/.claude/commands/ant/import-signals.md +71 -0
- package/.claude/commands/ant/init.md +349 -191
- package/.claude/commands/ant/insert-phase.md +105 -0
- package/.claude/commands/ant/interpret.md +11 -0
- package/.claude/commands/ant/lay-eggs.md +167 -158
- package/.claude/commands/ant/maturity.md +22 -11
- package/.claude/commands/ant/memory-details.md +77 -0
- package/.claude/commands/ant/migrate-state.md +6 -0
- package/.claude/commands/ant/oracle.md +317 -62
- package/.claude/commands/ant/organize.md +10 -5
- package/.claude/commands/ant/patrol.md +620 -0
- package/.claude/commands/ant/pause-colony.md +8 -22
- package/.claude/commands/ant/phase.md +26 -37
- package/.claude/commands/ant/pheromones.md +156 -0
- package/.claude/commands/ant/plan.md +199 -50
- package/.claude/commands/ant/preferences.md +65 -0
- package/.claude/commands/ant/quick.md +100 -0
- package/.claude/commands/ant/redirect.md +18 -0
- package/.claude/commands/ant/resume-colony.md +37 -22
- package/.claude/commands/ant/resume.md +60 -7
- package/.claude/commands/ant/run.md +231 -0
- package/.claude/commands/ant/seal.md +506 -78
- package/.claude/commands/ant/skill-create.md +286 -0
- package/.claude/commands/ant/status.md +171 -1
- package/.claude/commands/ant/swarm.md +11 -23
- package/.claude/commands/ant/tunnels.md +1 -0
- package/.claude/commands/ant/update.md +58 -135
- package/.claude/commands/ant/verify-castes.md +90 -42
- package/.claude/commands/ant/watch.md +1 -0
- package/.opencode/agents/aether-ambassador.md +1 -1
- package/.opencode/agents/aether-architect.md +133 -0
- package/.opencode/agents/aether-builder.md +3 -3
- package/.opencode/agents/aether-oracle.md +137 -0
- package/.opencode/agents/aether-queen.md +1 -1
- package/.opencode/agents/aether-route-setter.md +1 -1
- package/.opencode/agents/aether-scout.md +1 -1
- package/.opencode/agents/aether-surveyor-disciplines.md +6 -1
- package/.opencode/agents/aether-surveyor-nest.md +6 -1
- package/.opencode/agents/aether-surveyor-pathogens.md +6 -1
- package/.opencode/agents/aether-surveyor-provisions.md +6 -1
- package/.opencode/agents/aether-tracker.md +1 -1
- package/.opencode/agents/aether-watcher.md +1 -1
- package/.opencode/agents/aether-weaver.md +1 -1
- package/.opencode/commands/ant/archaeology.md +7 -14
- package/.opencode/commands/ant/build.md +54 -88
- package/.opencode/commands/ant/chaos.md +7 -24
- package/.opencode/commands/ant/colonize.md +10 -17
- package/.opencode/commands/ant/continue.md +595 -66
- package/.opencode/commands/ant/council.md +150 -18
- package/.opencode/commands/ant/data-clean.md +77 -0
- package/.opencode/commands/ant/dream.md +15 -17
- package/.opencode/commands/ant/entomb.md +28 -18
- package/.opencode/commands/ant/export-signals.md +54 -0
- package/.opencode/commands/ant/feedback.md +24 -5
- package/.opencode/commands/ant/flag.md +16 -4
- package/.opencode/commands/ant/flags.md +24 -10
- package/.opencode/commands/ant/focus.md +22 -5
- package/.opencode/commands/ant/help.md +41 -8
- package/.opencode/commands/ant/history.md +9 -0
- package/.opencode/commands/ant/import-signals.md +68 -0
- package/.opencode/commands/ant/init.md +396 -154
- package/.opencode/commands/ant/insert-phase.md +111 -0
- package/.opencode/commands/ant/interpret.md +16 -0
- package/.opencode/commands/ant/lay-eggs.md +184 -112
- package/.opencode/commands/ant/maturity.md +18 -2
- package/.opencode/commands/ant/memory-details.md +83 -0
- package/.opencode/commands/ant/migrate-state.md +12 -0
- package/.opencode/commands/ant/oracle.md +322 -67
- package/.opencode/commands/ant/organize.md +14 -12
- package/.opencode/commands/ant/patrol.md +626 -0
- package/.opencode/commands/ant/pause-colony.md +12 -29
- package/.opencode/commands/ant/phase.md +30 -40
- package/.opencode/commands/ant/pheromones.md +162 -0
- package/.opencode/commands/ant/plan.md +210 -57
- package/.opencode/commands/ant/preferences.md +71 -0
- package/.opencode/commands/ant/quick.md +91 -0
- package/.opencode/commands/ant/redirect.md +22 -5
- package/.opencode/commands/ant/resume-colony.md +41 -29
- package/.opencode/commands/ant/resume.md +80 -20
- package/.opencode/commands/ant/run.md +237 -0
- package/.opencode/commands/ant/seal.md +230 -25
- package/.opencode/commands/ant/skill-create.md +63 -0
- package/.opencode/commands/ant/status.md +125 -30
- package/.opencode/commands/ant/swarm.md +3 -345
- package/.opencode/commands/ant/tunnels.md +3 -9
- package/.opencode/commands/ant/update.md +63 -127
- package/.opencode/commands/ant/verify-castes.md +96 -42
- package/.opencode/commands/ant/watch.md +7 -0
- package/CHANGELOG.md +368 -1
- package/README.md +195 -324
- package/bin/cli.js +236 -429
- package/bin/generate-commands.js +186 -0
- package/bin/generate-commands.sh +128 -89
- package/bin/lib/spawn-logger.js +0 -15
- package/bin/lib/update-transaction.js +285 -35
- package/bin/npx-install.js +178 -0
- package/bin/validate-package.sh +85 -3
- package/package.json +16 -4
- package/.aether/CONTEXT.md +0 -160
- package/.aether/docs/QUEEN.md +0 -84
- package/.aether/exchange/colony-registry.xml +0 -11
- package/.aether/exchange/pheromones.xml +0 -87
- package/.aether/exchange/queen-wisdom.xml +0 -14
- package/.aether/model-profiles.yaml +0 -100
- package/.aether/utils/spawn-with-model.sh +0 -56
- package/bin/lib/model-profiles.js +0 -445
- package/bin/lib/model-verify.js +0 -288
- package/bin/lib/proxy-health.js +0 -253
- package/bin/lib/telemetry.js +0 -441
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Session utility functions -- extracted from aether-utils.sh
|
|
3
|
+
# Provides: _session_verify_fresh, _session_clear, _session_init, _session_update,
|
|
4
|
+
# _session_read, _session_is_stale, _session_clear_context,
|
|
5
|
+
# _session_mark_resumed, _session_summary
|
|
6
|
+
# Also includes: _rotate_spawn_tree (helper used only by _session_init)
|
|
7
|
+
|
|
8
|
+
# ============================================================================
|
|
9
|
+
# _session_verify_fresh
|
|
10
|
+
# Generic session freshness verification
|
|
11
|
+
# Usage: _session_verify_fresh [args...] (same args as before: --command <name> [--force] <session_start_unixtime>)
|
|
12
|
+
# Returns: JSON with pass/fail status and file details
|
|
13
|
+
# ============================================================================
|
|
14
|
+
_session_verify_fresh() {
|
|
15
|
+
# Parse arguments
|
|
16
|
+
local command_name=""
|
|
17
|
+
local force_mode=""
|
|
18
|
+
local session_start_time=""
|
|
19
|
+
|
|
20
|
+
while [[ $# -gt 0 ]]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--command) command_name="$2"; shift 2 ;;
|
|
23
|
+
--force) force_mode="--force"; shift ;;
|
|
24
|
+
*) session_start_time="$1"; shift ;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
# Validate command name
|
|
29
|
+
[[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-verify-fresh --command <name> [--force] <session_start>"
|
|
30
|
+
|
|
31
|
+
# Map command to directory and files (using env var override pattern)
|
|
32
|
+
local session_dir required_docs
|
|
33
|
+
case "$command_name" in
|
|
34
|
+
survey)
|
|
35
|
+
session_dir="${SURVEY_DIR:-.aether/data/survey}"
|
|
36
|
+
required_docs="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
|
|
37
|
+
;;
|
|
38
|
+
oracle)
|
|
39
|
+
session_dir="${ORACLE_DIR:-.aether/oracle}"
|
|
40
|
+
required_docs="state.json plan.json gaps.md synthesis.md research-plan.md"
|
|
41
|
+
;;
|
|
42
|
+
watch)
|
|
43
|
+
session_dir="${WATCH_DIR:-.aether/data}"
|
|
44
|
+
required_docs="watch-status.txt watch-progress.txt"
|
|
45
|
+
;;
|
|
46
|
+
swarm)
|
|
47
|
+
session_dir="${SWARM_DIR:-.aether/data/swarm}"
|
|
48
|
+
required_docs="findings.json"
|
|
49
|
+
;;
|
|
50
|
+
init)
|
|
51
|
+
session_dir="${INIT_DIR:-.aether/data}"
|
|
52
|
+
required_docs="COLONY_STATE.json constraints.json"
|
|
53
|
+
;;
|
|
54
|
+
seal|entomb)
|
|
55
|
+
session_dir="${ARCHIVE_DIR:-.aether/data/archive}"
|
|
56
|
+
required_docs="manifest.json"
|
|
57
|
+
;;
|
|
58
|
+
*)
|
|
59
|
+
json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name" '{"commands":["survey","oracle","watch","swarm","init","seal","entomb"]}'
|
|
60
|
+
;;
|
|
61
|
+
esac
|
|
62
|
+
|
|
63
|
+
# Initialize result arrays
|
|
64
|
+
local fresh_docs=""
|
|
65
|
+
local stale_docs=""
|
|
66
|
+
local missing_docs=""
|
|
67
|
+
local total_lines=0
|
|
68
|
+
|
|
69
|
+
for doc in $required_docs; do
|
|
70
|
+
local doc_path="$session_dir/$doc"
|
|
71
|
+
|
|
72
|
+
if [[ ! -f "$doc_path" ]]; then
|
|
73
|
+
missing_docs="${missing_docs:+$missing_docs }$doc"
|
|
74
|
+
continue
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Get line count
|
|
78
|
+
local lines
|
|
79
|
+
lines=$(wc -l < "$doc_path" 2>/dev/null | tr -d ' ' || echo "0") # SUPPRESS:OK -- read-default: file may not exist
|
|
80
|
+
total_lines=$((total_lines + lines))
|
|
81
|
+
|
|
82
|
+
# In force mode, accept any existing file
|
|
83
|
+
if [[ "$force_mode" == "--force" ]]; then
|
|
84
|
+
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
85
|
+
continue
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# Check timestamp if session_start_time provided
|
|
89
|
+
if [[ -n "$session_start_time" ]]; then
|
|
90
|
+
# Cross-platform stat: macOS uses -f %m, Linux uses -c %Y
|
|
91
|
+
local file_mtime
|
|
92
|
+
file_mtime=$(stat -f %m "$doc_path" 2>/dev/null || stat -c %Y "$doc_path" 2>/dev/null || echo "0") # SUPPRESS:OK -- cross-platform: macOS stat syntax
|
|
93
|
+
|
|
94
|
+
if [[ "$file_mtime" -ge "$session_start_time" ]]; then
|
|
95
|
+
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
96
|
+
else
|
|
97
|
+
stale_docs="${stale_docs:+$stale_docs }$doc"
|
|
98
|
+
fi
|
|
99
|
+
else
|
|
100
|
+
# No start time provided - accept existing file (backward compatible)
|
|
101
|
+
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
102
|
+
fi
|
|
103
|
+
done
|
|
104
|
+
|
|
105
|
+
# Determine pass/fail
|
|
106
|
+
# pass = true if: no stale files (fresh files can coexist with missing files)
|
|
107
|
+
# missing files are ok - they will be created during the session
|
|
108
|
+
local pass=false
|
|
109
|
+
if [[ "$force_mode" == "--force" ]] || [[ -z "$stale_docs" ]]; then
|
|
110
|
+
pass=true
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# Build JSON response
|
|
114
|
+
local fresh_json=""
|
|
115
|
+
for item in $fresh_docs; do fresh_json="$fresh_json\"$item\","; done
|
|
116
|
+
fresh_json="[${fresh_json%,}]"
|
|
117
|
+
|
|
118
|
+
local stale_json=""
|
|
119
|
+
for item in $stale_docs; do stale_json="$stale_json\"$item\","; done
|
|
120
|
+
stale_json="[${stale_json%,}]"
|
|
121
|
+
|
|
122
|
+
local missing_json=""
|
|
123
|
+
for item in $missing_docs; do missing_json="$missing_json\"$item\","; done
|
|
124
|
+
missing_json="[${missing_json%,}]"
|
|
125
|
+
|
|
126
|
+
echo "$(jq -n --argjson ok "$pass" --arg command "$command_name" \
|
|
127
|
+
--argjson fresh "$fresh_json" --argjson stale "$stale_json" \
|
|
128
|
+
--argjson missing "$missing_json" --argjson total_lines "$total_lines" \
|
|
129
|
+
'{ok: $ok, command: $command, fresh: $fresh, stale: $stale, missing: $missing, total_lines: $total_lines}')"
|
|
130
|
+
exit 0
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# ============================================================================
|
|
134
|
+
# _session_clear
|
|
135
|
+
# Generic session file clearing
|
|
136
|
+
# Usage: _session_clear [args...] (same args as before: --command <name> [--dry-run])
|
|
137
|
+
# ============================================================================
|
|
138
|
+
_session_clear() {
|
|
139
|
+
# Parse arguments
|
|
140
|
+
local command_name=""
|
|
141
|
+
local dry_run=""
|
|
142
|
+
|
|
143
|
+
while [[ $# -gt 0 ]]; do
|
|
144
|
+
case "$1" in
|
|
145
|
+
--command) command_name="$2"; shift 2 ;;
|
|
146
|
+
--dry-run) dry_run="--dry-run"; shift ;;
|
|
147
|
+
*) shift ;;
|
|
148
|
+
esac
|
|
149
|
+
done
|
|
150
|
+
|
|
151
|
+
[[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-clear --command <name> [--dry-run]"
|
|
152
|
+
|
|
153
|
+
# Map command to directory and files
|
|
154
|
+
local session_dir="" files="" subdir_files=""
|
|
155
|
+
case "$command_name" in
|
|
156
|
+
survey)
|
|
157
|
+
session_dir="${SURVEY_DIR:-.aether/data/survey}"
|
|
158
|
+
files="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
|
|
159
|
+
;;
|
|
160
|
+
oracle)
|
|
161
|
+
session_dir="${ORACLE_DIR:-.aether/oracle}"
|
|
162
|
+
files="state.json plan.json gaps.md synthesis.md research-plan.md .stop .last-topic"
|
|
163
|
+
# Also clear discoveries subdirectory
|
|
164
|
+
subdir_files="discoveries/*"
|
|
165
|
+
;;
|
|
166
|
+
watch)
|
|
167
|
+
session_dir="${WATCH_DIR:-.aether/data}"
|
|
168
|
+
files="watch-status.txt watch-progress.txt"
|
|
169
|
+
;;
|
|
170
|
+
swarm)
|
|
171
|
+
session_dir="${SWARM_DIR:-.aether/data/swarm}"
|
|
172
|
+
files="findings.json display.json timing.json"
|
|
173
|
+
;;
|
|
174
|
+
init)
|
|
175
|
+
# Init clear is destructive - blocked for auto-clear
|
|
176
|
+
json_err "$E_VALIDATION_FAILED" "Command 'init' is protected and cannot be auto-cleared. Use manual removal of COLONY_STATE.json if absolutely necessary."
|
|
177
|
+
;;
|
|
178
|
+
seal|entomb)
|
|
179
|
+
# Archive operations should never be auto-cleared
|
|
180
|
+
json_err "$E_VALIDATION_FAILED" "Command '$command_name' is protected and cannot be auto-cleared. Archives and chambers must be managed manually."
|
|
181
|
+
;;
|
|
182
|
+
*)
|
|
183
|
+
json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name"
|
|
184
|
+
;;
|
|
185
|
+
esac
|
|
186
|
+
|
|
187
|
+
local cleared=""
|
|
188
|
+
local errors=""
|
|
189
|
+
|
|
190
|
+
if [[ -d "$session_dir" && -n "$files" ]]; then
|
|
191
|
+
for doc in $files; do
|
|
192
|
+
local doc_path="$session_dir/$doc"
|
|
193
|
+
if [[ -f "$doc_path" ]]; then
|
|
194
|
+
if [[ "$dry_run" == "--dry-run" ]]; then
|
|
195
|
+
cleared="$cleared $doc"
|
|
196
|
+
else
|
|
197
|
+
if rm -f "$doc_path" 2>/dev/null; then # SUPPRESS:OK -- cleanup: file may not exist
|
|
198
|
+
cleared="$cleared $doc"
|
|
199
|
+
else
|
|
200
|
+
errors="$errors $doc"
|
|
201
|
+
fi
|
|
202
|
+
fi
|
|
203
|
+
fi
|
|
204
|
+
done
|
|
205
|
+
|
|
206
|
+
# Handle oracle discoveries subdirectory
|
|
207
|
+
if [[ "$command_name" == "oracle" && -d "$session_dir/discoveries" ]]; then
|
|
208
|
+
if [[ "$dry_run" == "--dry-run" ]]; then
|
|
209
|
+
cleared="$cleared discoveries/"
|
|
210
|
+
else
|
|
211
|
+
# SUPPRESS:OK -- cleanup: file may not exist
|
|
212
|
+
rm -rf "$session_dir/discoveries" 2>/dev/null && cleared="$cleared discoveries/" || errors="$errors discoveries/"
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
local dry_run_bool=$([[ "$dry_run" == "--dry-run" ]] && echo "true" || echo "false")
|
|
218
|
+
json_ok "$(jq -n --arg command "$command_name" --arg cleared "${cleared// /}" \
|
|
219
|
+
--arg errors "${errors// /}" --argjson dry_run "$dry_run_bool" \
|
|
220
|
+
'{command: $command, cleared: $cleared, errors: $errors, dry_run: $dry_run}')"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# ============================================================================
|
|
224
|
+
# _rotate_spawn_tree (helper -- used only by _session_init)
|
|
225
|
+
# ARCH-03: Rotate spawn-tree.txt at session start to prevent unbounded growth.
|
|
226
|
+
# Archives previous session's tree to a timestamped file; caps archive count at 5.
|
|
227
|
+
# ============================================================================
|
|
228
|
+
_rotate_spawn_tree() {
|
|
229
|
+
local tree_file="$COLONY_DATA_DIR/spawn-tree.txt"
|
|
230
|
+
[[ -f "$tree_file" ]] && [[ -s "$tree_file" ]] || return 0
|
|
231
|
+
mkdir -p "$COLONY_DATA_DIR/spawn-tree-archive"
|
|
232
|
+
local archive_ts
|
|
233
|
+
archive_ts=$(date +%Y%m%d_%H%M%S)
|
|
234
|
+
if ! cp "$tree_file" "$COLONY_DATA_DIR/spawn-tree-archive/spawn-tree.${archive_ts}.txt" 2>/dev/null; then # SUPPRESS:OK -- cleanup: backup copy is best-effort
|
|
235
|
+
_aether_log_error "Could not archive spawn-tree before rotation"
|
|
236
|
+
fi
|
|
237
|
+
> "$tree_file" # Truncate in-place — preserves file handle for tail -f watchers
|
|
238
|
+
# Keep only 5 archives
|
|
239
|
+
# SUPPRESS:OK -- read-default: directory may not exist
|
|
240
|
+
# SUPPRESS:OK -- cleanup: rotation cleanup is best-effort
|
|
241
|
+
ls -t "$COLONY_DATA_DIR/spawn-tree-archive"/spawn-tree.*.txt 2>/dev/null \
|
|
242
|
+
| tail -n +6 | while IFS= read -r file; do rm -f "$file"; done 2>/dev/null || true # SUPPRESS:OK -- cleanup: file may not exist
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# ============================================================================
|
|
246
|
+
# _session_init
|
|
247
|
+
# Initialize a new session tracking file
|
|
248
|
+
# Usage: _session_init [session_id] [goal]
|
|
249
|
+
# ============================================================================
|
|
250
|
+
_session_init() {
|
|
251
|
+
local session_id="${1:-$(date +%s)_$(openssl rand -hex 4 2>/dev/null || echo $$)}" # SUPPRESS:OK -- read-default: openssl may not be available
|
|
252
|
+
local goal="${2:-}"
|
|
253
|
+
|
|
254
|
+
_rotate_spawn_tree
|
|
255
|
+
|
|
256
|
+
local session_file="$COLONY_DATA_DIR/session.json"
|
|
257
|
+
local baseline
|
|
258
|
+
baseline=$(git rev-parse HEAD 2>/dev/null || echo "") # SUPPRESS:OK -- read-default: may not have commits yet
|
|
259
|
+
|
|
260
|
+
jq -n --arg sid "$session_id" --arg started "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
261
|
+
--arg goal "$goal" --arg baseline "$baseline" \
|
|
262
|
+
'{
|
|
263
|
+
session_id: $sid,
|
|
264
|
+
started_at: $started,
|
|
265
|
+
last_command: null,
|
|
266
|
+
last_command_at: null,
|
|
267
|
+
colony_goal: $goal,
|
|
268
|
+
current_phase: 0,
|
|
269
|
+
current_milestone: "First Mound",
|
|
270
|
+
suggested_next: "/ant:plan",
|
|
271
|
+
context_cleared: false,
|
|
272
|
+
baseline_commit: $baseline,
|
|
273
|
+
resumed_at: null,
|
|
274
|
+
active_todos: [],
|
|
275
|
+
summary: "Session initialized"
|
|
276
|
+
}' > "$session_file.tmp"
|
|
277
|
+
mv "$session_file.tmp" "$session_file"
|
|
278
|
+
json_ok "$(jq -n --arg sid "$session_id" --arg goal "$goal" --arg file "$session_file" \
|
|
279
|
+
'{session_id: $sid, goal: $goal, file: $file}')"
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# ============================================================================
|
|
283
|
+
# _session_update
|
|
284
|
+
# Update session with latest activity
|
|
285
|
+
# Usage: _session_update <command> [suggested_next] [summary]
|
|
286
|
+
# ============================================================================
|
|
287
|
+
_session_update() {
|
|
288
|
+
local cmd_run="${1:-}"
|
|
289
|
+
local suggested="${2:-}"
|
|
290
|
+
local summary="${3:-}"
|
|
291
|
+
|
|
292
|
+
local session_file="$COLONY_DATA_DIR/session.json"
|
|
293
|
+
|
|
294
|
+
if [[ ! -f "$session_file" ]]; then
|
|
295
|
+
# Auto-initialize if doesn't exist
|
|
296
|
+
bash "$SCRIPT_DIR/aether-utils.sh" session-init "auto_$(date +%s)" ""
|
|
297
|
+
fi
|
|
298
|
+
|
|
299
|
+
# Read current session
|
|
300
|
+
local current_session
|
|
301
|
+
current_session=$(cat "$session_file" 2>/dev/null || echo '{}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
302
|
+
|
|
303
|
+
# Extract current values for preservation
|
|
304
|
+
local current_goal current_phase current_milestone
|
|
305
|
+
current_goal=$(sanitize_read_value "$(echo "$current_session" | jq -r '.colony_goal // empty')")
|
|
306
|
+
current_phase=$(echo "$current_session" | jq -r '.current_phase // 0')
|
|
307
|
+
current_milestone=$(echo "$current_session" | jq -r '.current_milestone // "First Mound"')
|
|
308
|
+
|
|
309
|
+
# Get top 3 TODOs if TO-DOs.md exists
|
|
310
|
+
local todos="[]"
|
|
311
|
+
if [[ -f "TO-DOs.md" ]]; then
|
|
312
|
+
todos=$(grep "^### " TO-DOs.md 2>/dev/null | head -3 | sed 's/^### //' | jq -R . | jq -s .) # SUPPRESS:OK -- existence-test: file may not exist
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
# MIGRATE: direct COLONY_STATE.json access -- use _state_read_field instead
|
|
316
|
+
# Get colony state if exists
|
|
317
|
+
if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
|
|
318
|
+
# SUPPRESS:OK -- read-default: query may return empty
|
|
319
|
+
current_goal=$(sanitize_read_value "$(jq -r '.goal // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_goal")")
|
|
320
|
+
# SUPPRESS:OK -- read-default: query may return empty
|
|
321
|
+
current_phase=$(jq -r '.current_phase // 0' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_phase")
|
|
322
|
+
# SUPPRESS:OK -- read-default: query may return empty
|
|
323
|
+
current_milestone=$(jq -r '.milestone // "First Mound"' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_milestone")
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# Capture current git HEAD for drift detection
|
|
327
|
+
local baseline
|
|
328
|
+
baseline=$(git rev-parse HEAD 2>/dev/null || echo "") # SUPPRESS:OK -- read-default: may not have commits yet
|
|
329
|
+
|
|
330
|
+
# Build updated session
|
|
331
|
+
echo "$current_session" | jq --arg cmd "$cmd_run" \
|
|
332
|
+
--arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
333
|
+
--arg suggested "$suggested" \
|
|
334
|
+
--arg summary "$summary" \
|
|
335
|
+
--arg goal "$current_goal" \
|
|
336
|
+
--argjson phase "$current_phase" \
|
|
337
|
+
--arg milestone "$current_milestone" \
|
|
338
|
+
--argjson todos "$todos" \
|
|
339
|
+
--arg baseline "$baseline" \
|
|
340
|
+
'.last_command = $cmd |
|
|
341
|
+
.last_command_at = $ts |
|
|
342
|
+
.suggested_next = $suggested |
|
|
343
|
+
.summary = $summary |
|
|
344
|
+
.colony_goal = $goal |
|
|
345
|
+
.current_phase = $phase |
|
|
346
|
+
.current_milestone = $milestone |
|
|
347
|
+
.active_todos = $todos |
|
|
348
|
+
.baseline_commit = $baseline' > "$session_file.tmp" || {
|
|
349
|
+
_aether_log_error "Could not process session update"
|
|
350
|
+
rm -f "$session_file.tmp"
|
|
351
|
+
json_err "$E_UNKNOWN" "Failed to update session file"
|
|
352
|
+
}
|
|
353
|
+
[[ -s "$session_file.tmp" ]] || {
|
|
354
|
+
_aether_log_error "Session update produced empty result -- not overwriting"
|
|
355
|
+
rm -f "$session_file.tmp"
|
|
356
|
+
json_err "$E_JSON_INVALID" "Session update produced empty result"
|
|
357
|
+
}
|
|
358
|
+
mv "$session_file.tmp" "$session_file" || {
|
|
359
|
+
_aether_log_error "Could not finalize session file update"
|
|
360
|
+
rm -f "$session_file.tmp"
|
|
361
|
+
json_err "$E_UNKNOWN" "Failed to rename temporary session file"
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
json_ok "$(jq -n --arg cmd "$cmd_run" '{updated: true, command: $cmd}')"
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# ============================================================================
|
|
368
|
+
# _session_read
|
|
369
|
+
# Read and return current session state
|
|
370
|
+
# ============================================================================
|
|
371
|
+
_session_read() {
|
|
372
|
+
local session_file="$COLONY_DATA_DIR/session.json"
|
|
373
|
+
|
|
374
|
+
if [[ ! -f "$session_file" ]]; then
|
|
375
|
+
json_ok "{\"exists\":false,\"session\":null}"
|
|
376
|
+
exit 0
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
local session_data
|
|
380
|
+
session_data=$(cat "$session_file" 2>/dev/null || echo '{}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
381
|
+
|
|
382
|
+
# Check if stale (> 24 hours)
|
|
383
|
+
local last_cmd_ts="" is_stale="" age_hours=""
|
|
384
|
+
last_cmd_ts=$(echo "$session_data" | jq -r '.last_command_at // .started_at // empty')
|
|
385
|
+
if [[ -n "$last_cmd_ts" ]]; then
|
|
386
|
+
local last_epoch=0 now_epoch=0
|
|
387
|
+
# SUPPRESS:OK -- cross-platform: macOS date syntax
|
|
388
|
+
# SUPPRESS:OK -- cross-platform: macOS vs Linux date/stat flags
|
|
389
|
+
last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null \
|
|
390
|
+
|| date -d "$last_cmd_ts" +%s 2>/dev/null \
|
|
391
|
+
|| echo 0)
|
|
392
|
+
now_epoch=$(date +%s)
|
|
393
|
+
age_hours=$(( (now_epoch - last_epoch) / 3600 ))
|
|
394
|
+
[[ $age_hours -gt 24 ]] && is_stale=true || is_stale=false
|
|
395
|
+
else
|
|
396
|
+
is_stale="false"
|
|
397
|
+
age_hours="unknown"
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
json_ok "$(jq -n --argjson is_stale "$is_stale" --argjson age "$age_hours" \
|
|
401
|
+
--argjson session "$session_data" \
|
|
402
|
+
'{exists: true, is_stale: $is_stale, age_hours: $age, session: $session}')"
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
# ============================================================================
|
|
406
|
+
# _session_is_stale
|
|
407
|
+
# Check if session is stale (returns JSON with is_stale boolean)
|
|
408
|
+
# ============================================================================
|
|
409
|
+
_session_is_stale() {
|
|
410
|
+
_deprecation_warning "session-is-stale"
|
|
411
|
+
local session_file="$COLONY_DATA_DIR/session.json"
|
|
412
|
+
|
|
413
|
+
if [[ ! -f "$session_file" ]]; then
|
|
414
|
+
json_ok '{"is_stale":true}'
|
|
415
|
+
exit 0
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
local last_cmd_ts
|
|
419
|
+
last_cmd_ts=$(jq -r '.last_command_at // .started_at // empty' "$session_file" 2>/dev/null) # SUPPRESS:OK -- read-default: file may not exist yet
|
|
420
|
+
|
|
421
|
+
if [[ -z "$last_cmd_ts" ]]; then
|
|
422
|
+
json_ok '{"is_stale":true}'
|
|
423
|
+
exit 0
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
# macOS uses -j -f, Linux uses -d
|
|
427
|
+
# SUPPRESS:OK -- cross-platform: macOS date syntax
|
|
428
|
+
# SUPPRESS:OK -- cross-platform: macOS vs Linux date/stat flags
|
|
429
|
+
local last_epoch now_epoch age_hours
|
|
430
|
+
last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null \
|
|
431
|
+
|| date -d "$last_cmd_ts" +%s 2>/dev/null \
|
|
432
|
+
|| echo 0)
|
|
433
|
+
now_epoch=$(date +%s)
|
|
434
|
+
age_hours=$(( (now_epoch - last_epoch) / 3600 ))
|
|
435
|
+
|
|
436
|
+
if [[ $age_hours -gt 24 ]]; then
|
|
437
|
+
json_ok '{"is_stale":true}'
|
|
438
|
+
else
|
|
439
|
+
json_ok '{"is_stale":false}'
|
|
440
|
+
fi
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# ============================================================================
|
|
444
|
+
# _session_clear_context
|
|
445
|
+
# Mark session context as cleared (preserves file but marks context_cleared)
|
|
446
|
+
# ============================================================================
|
|
447
|
+
_session_clear_context() {
|
|
448
|
+
_deprecation_warning "session-clear-context"
|
|
449
|
+
local preserve="${1:-false}"
|
|
450
|
+
local session_file="$COLONY_DATA_DIR/session.json"
|
|
451
|
+
|
|
452
|
+
if [[ -f "$session_file" ]]; then
|
|
453
|
+
if [[ "$preserve" == "true" ]]; then
|
|
454
|
+
# Just mark as cleared
|
|
455
|
+
jq '.context_cleared = true' "$session_file" > "$session_file.tmp" || {
|
|
456
|
+
_aether_log_error "Could not mark session as cleared"
|
|
457
|
+
rm -f "$session_file.tmp"
|
|
458
|
+
}
|
|
459
|
+
if [[ -s "$session_file.tmp" ]]; then
|
|
460
|
+
mv "$session_file.tmp" "$session_file" || _aether_log_error "Could not finalize session clear"
|
|
461
|
+
fi
|
|
462
|
+
json_ok "{\"cleared\":true,\"preserved\":true}"
|
|
463
|
+
else
|
|
464
|
+
# Remove file entirely
|
|
465
|
+
rm -f "$session_file"
|
|
466
|
+
json_ok "{\"cleared\":true,\"preserved\":false}"
|
|
467
|
+
fi
|
|
468
|
+
else
|
|
469
|
+
json_ok "{\"cleared\":false,\"reason\":\"no_session_exists\"}"
|
|
470
|
+
fi
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
# ============================================================================
|
|
474
|
+
# _session_mark_resumed
|
|
475
|
+
# Mark session as resumed
|
|
476
|
+
# ============================================================================
|
|
477
|
+
_session_mark_resumed() {
|
|
478
|
+
local session_file="$COLONY_DATA_DIR/session.json"
|
|
479
|
+
|
|
480
|
+
if [[ -f "$session_file" ]]; then
|
|
481
|
+
jq --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
482
|
+
'.resumed_at = $ts | .context_cleared = false' "$session_file" > "$session_file.tmp" || {
|
|
483
|
+
_aether_log_error "Could not process session resume update"
|
|
484
|
+
rm -f "$session_file.tmp"
|
|
485
|
+
}
|
|
486
|
+
if [[ -s "$session_file.tmp" ]]; then
|
|
487
|
+
mv "$session_file.tmp" "$session_file" || _aether_log_error "Could not finalize session resume"
|
|
488
|
+
fi
|
|
489
|
+
json_ok "$(jq -n --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" '{resumed: true, timestamp: $ts}')"
|
|
490
|
+
else
|
|
491
|
+
json_err "$E_RESOURCE_NOT_FOUND" "No active session to mark as resumed. Try: run /ant:init to start a new session."
|
|
492
|
+
fi
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
# ============================================================================
|
|
496
|
+
# _session_summary
|
|
497
|
+
# Get session summary (human-readable or JSON)
|
|
498
|
+
# ============================================================================
|
|
499
|
+
_session_summary() {
|
|
500
|
+
_deprecation_warning "session-summary"
|
|
501
|
+
local session_file="$COLONY_DATA_DIR/session.json"
|
|
502
|
+
local json_mode="false"
|
|
503
|
+
|
|
504
|
+
# Parse --json flag (command name already shifted by main dispatch)
|
|
505
|
+
while [[ $# -gt 0 ]]; do
|
|
506
|
+
case "$1" in
|
|
507
|
+
--json)
|
|
508
|
+
json_mode="true"
|
|
509
|
+
shift
|
|
510
|
+
;;
|
|
511
|
+
*)
|
|
512
|
+
shift
|
|
513
|
+
;;
|
|
514
|
+
esac
|
|
515
|
+
done
|
|
516
|
+
|
|
517
|
+
if [[ ! -f "$session_file" ]]; then
|
|
518
|
+
if [[ "$json_mode" == "true" ]]; then
|
|
519
|
+
json_ok '{"exists":false,"goal":null,"phase":0}'
|
|
520
|
+
else
|
|
521
|
+
echo "No active session found."
|
|
522
|
+
fi
|
|
523
|
+
exit 0
|
|
524
|
+
fi
|
|
525
|
+
|
|
526
|
+
local goal phase milestone last_cmd last_at suggested cleared
|
|
527
|
+
goal=$(sanitize_read_value "$(jq -r '.colony_goal // "No goal set"' "$session_file")")
|
|
528
|
+
phase=$(jq -r '.current_phase // 0' "$session_file")
|
|
529
|
+
milestone=$(jq -r '.current_milestone // "First Mound"' "$session_file")
|
|
530
|
+
last_cmd=$(jq -r '.last_command // "None"' "$session_file")
|
|
531
|
+
last_at=$(jq -r '.last_command_at // "Unknown"' "$session_file")
|
|
532
|
+
suggested=$(jq -r '.suggested_next // "None"' "$session_file")
|
|
533
|
+
cleared=$(jq -r '.context_cleared // false' "$session_file")
|
|
534
|
+
|
|
535
|
+
if [[ "$json_mode" == "true" ]]; then
|
|
536
|
+
json_ok "$(jq -n --arg goal "$goal" --argjson phase "$phase" \
|
|
537
|
+
--arg milestone "$milestone" --arg last_cmd "$last_cmd" \
|
|
538
|
+
--arg last_at "$last_at" --arg suggested "$suggested" \
|
|
539
|
+
--argjson cleared "$cleared" \
|
|
540
|
+
'{exists: true, goal: $goal, phase: $phase, milestone: $milestone, last_command: $last_cmd, last_active: $last_at, suggested_next: $suggested, context_cleared: $cleared}')"
|
|
541
|
+
else
|
|
542
|
+
echo "Session Summary"
|
|
543
|
+
echo "=================="
|
|
544
|
+
echo "Goal: $goal"
|
|
545
|
+
[[ "$phase" != "0" ]] && echo "Phase: $phase"
|
|
546
|
+
echo "Milestone: $milestone"
|
|
547
|
+
echo "Last Command: $last_cmd"
|
|
548
|
+
echo "Last Active: $last_at"
|
|
549
|
+
[[ "$suggested" != "None" ]] && echo "Suggested Next: $suggested"
|
|
550
|
+
[[ "$cleared" == "true" ]] && echo "Status: Context was cleared"
|
|
551
|
+
fi
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
# ============================================================================
|
|
555
|
+
# _pending_decision_add
|
|
556
|
+
# Add a decision to the pending decisions queue
|
|
557
|
+
# Usage: pending-decision-add --type <type> --description <desc> [--phase N] [--source <src>]
|
|
558
|
+
# Types: visual_checkpoint, replan, escalation, runtime_verification, user_input
|
|
559
|
+
# ============================================================================
|
|
560
|
+
_pending_decision_add() {
|
|
561
|
+
local pd_type=""
|
|
562
|
+
local pd_description=""
|
|
563
|
+
local pd_phase="null"
|
|
564
|
+
local pd_source=""
|
|
565
|
+
|
|
566
|
+
while [[ $# -gt 0 ]]; do
|
|
567
|
+
case "$1" in
|
|
568
|
+
--type) pd_type="$2"; shift 2 ;;
|
|
569
|
+
--description) pd_description="$2"; shift 2 ;;
|
|
570
|
+
--phase) pd_phase="$2"; shift 2 ;;
|
|
571
|
+
--source) pd_source="$2"; shift 2 ;;
|
|
572
|
+
*) shift ;;
|
|
573
|
+
esac
|
|
574
|
+
done
|
|
575
|
+
|
|
576
|
+
[[ -z "$pd_type" ]] && json_err "$E_VALIDATION_FAILED" "pending-decision-add requires --type"
|
|
577
|
+
[[ -z "$pd_description" ]] && json_err "$E_VALIDATION_FAILED" "pending-decision-add requires --description"
|
|
578
|
+
|
|
579
|
+
local pd_file="$COLONY_DATA_DIR/pending-decisions.json"
|
|
580
|
+
local pd_id="pd_$(date +%s)_$$"
|
|
581
|
+
local pd_now
|
|
582
|
+
pd_now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
583
|
+
|
|
584
|
+
# Acquire lock for concurrent access
|
|
585
|
+
if type acquire_lock &>/dev/null; then
|
|
586
|
+
acquire_lock "$pd_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on pending-decisions.json"
|
|
587
|
+
trap 'release_lock 2>/dev/null || true' EXIT # SUPPRESS:OK -- cleanup: lock may not be held
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
# Initialize file if missing
|
|
591
|
+
if [[ ! -f "$pd_file" ]]; then
|
|
592
|
+
echo '{"version":"1.0","decisions":[]}' > "$pd_file"
|
|
593
|
+
fi
|
|
594
|
+
|
|
595
|
+
local pd_current
|
|
596
|
+
pd_current=$(cat "$pd_file" 2>/dev/null || echo '{"version":"1.0","decisions":[]}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
597
|
+
|
|
598
|
+
# Build new decision entry
|
|
599
|
+
local pd_phase_val
|
|
600
|
+
if [[ "$pd_phase" == "null" ]]; then
|
|
601
|
+
pd_phase_val="null"
|
|
602
|
+
else
|
|
603
|
+
pd_phase_val="$pd_phase"
|
|
604
|
+
fi
|
|
605
|
+
|
|
606
|
+
local pd_updated
|
|
607
|
+
pd_updated=$(echo "$pd_current" | jq \
|
|
608
|
+
--arg id "$pd_id" \
|
|
609
|
+
--arg type "$pd_type" \
|
|
610
|
+
--arg description "$pd_description" \
|
|
611
|
+
--argjson phase "${pd_phase_val}" \
|
|
612
|
+
--arg source "$pd_source" \
|
|
613
|
+
--arg created_at "$pd_now" \
|
|
614
|
+
'.decisions += [{
|
|
615
|
+
id: $id,
|
|
616
|
+
type: $type,
|
|
617
|
+
description: $description,
|
|
618
|
+
phase: $phase,
|
|
619
|
+
source: $source,
|
|
620
|
+
created_at: $created_at,
|
|
621
|
+
resolved: false
|
|
622
|
+
}]' 2>/dev/null) || {
|
|
623
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
624
|
+
json_err "$E_JSON_INVALID" "Failed to append decision to pending-decisions.json"
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
atomic_write "$pd_file" "$pd_updated" || {
|
|
628
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
629
|
+
json_err "$E_JSON_INVALID" "Failed to write pending-decisions.json"
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
type release_lock &>/dev/null && { release_lock 2>/dev/null || true; trap - EXIT; } # SUPPRESS:OK -- cleanup: lock may not be held
|
|
633
|
+
|
|
634
|
+
local pd_count
|
|
635
|
+
pd_count=$(echo "$pd_updated" | jq '.decisions | length')
|
|
636
|
+
|
|
637
|
+
json_ok "$(jq -n --arg id "$pd_id" --argjson count "$pd_count" \
|
|
638
|
+
'{id: $id, decision_count: $count}')"
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
# ============================================================================
|
|
642
|
+
# _pending_decision_list
|
|
643
|
+
# List decisions from the pending decisions queue
|
|
644
|
+
# Usage: pending-decision-list [--unresolved] [--type <type>]
|
|
645
|
+
# Default: show only unresolved
|
|
646
|
+
# ============================================================================
|
|
647
|
+
_pending_decision_list() {
|
|
648
|
+
local pd_unresolved_only="true"
|
|
649
|
+
local pd_filter_type=""
|
|
650
|
+
|
|
651
|
+
while [[ $# -gt 0 ]]; do
|
|
652
|
+
case "$1" in
|
|
653
|
+
--unresolved) pd_unresolved_only="true"; shift ;;
|
|
654
|
+
--type) pd_filter_type="$2"; shift 2 ;;
|
|
655
|
+
*) shift ;;
|
|
656
|
+
esac
|
|
657
|
+
done
|
|
658
|
+
|
|
659
|
+
local pd_file="$COLONY_DATA_DIR/pending-decisions.json"
|
|
660
|
+
|
|
661
|
+
if [[ ! -f "$pd_file" ]]; then
|
|
662
|
+
json_ok '{"total":0,"unresolved":0,"decisions":[]}'
|
|
663
|
+
exit 0
|
|
664
|
+
fi
|
|
665
|
+
|
|
666
|
+
local pd_data
|
|
667
|
+
pd_data=$(cat "$pd_file" 2>/dev/null || echo '{"version":"1.0","decisions":[]}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
668
|
+
|
|
669
|
+
# Build jq filter
|
|
670
|
+
local pd_filter='.decisions'
|
|
671
|
+
|
|
672
|
+
# Apply type filter if provided
|
|
673
|
+
if [[ -n "$pd_filter_type" ]]; then
|
|
674
|
+
pd_filter="$pd_filter | map(select(.type == \"$pd_filter_type\"))"
|
|
675
|
+
fi
|
|
676
|
+
|
|
677
|
+
# Apply resolved filter (default: only unresolved)
|
|
678
|
+
if [[ "$pd_unresolved_only" == "true" ]]; then
|
|
679
|
+
pd_filter="$pd_filter | map(select(.resolved == false))"
|
|
680
|
+
fi
|
|
681
|
+
|
|
682
|
+
local pd_total pd_unresolved pd_decisions
|
|
683
|
+
pd_total=$(echo "$pd_data" | jq '.decisions | length')
|
|
684
|
+
pd_unresolved=$(echo "$pd_data" | jq '[.decisions[] | select(.resolved == false)] | length')
|
|
685
|
+
pd_decisions=$(echo "$pd_data" | jq "$pd_filter")
|
|
686
|
+
|
|
687
|
+
json_ok "$(jq -n --argjson total "$pd_total" --argjson unresolved "$pd_unresolved" \
|
|
688
|
+
--argjson decisions "$pd_decisions" \
|
|
689
|
+
'{total: $total, unresolved: $unresolved, decisions: $decisions}')"
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
# ============================================================================
|
|
693
|
+
# _pending_decision_resolve
|
|
694
|
+
# Mark a pending decision as resolved
|
|
695
|
+
# Usage: pending-decision-resolve --id <id> --resolution <text>
|
|
696
|
+
# ============================================================================
|
|
697
|
+
_pending_decision_resolve() {
|
|
698
|
+
local pd_id=""
|
|
699
|
+
local pd_resolution=""
|
|
700
|
+
|
|
701
|
+
while [[ $# -gt 0 ]]; do
|
|
702
|
+
case "$1" in
|
|
703
|
+
--id) pd_id="$2"; shift 2 ;;
|
|
704
|
+
--resolution) pd_resolution="$2"; shift 2 ;;
|
|
705
|
+
*) shift ;;
|
|
706
|
+
esac
|
|
707
|
+
done
|
|
708
|
+
|
|
709
|
+
[[ -z "$pd_id" ]] && json_err "$E_VALIDATION_FAILED" "pending-decision-resolve requires --id"
|
|
710
|
+
[[ -z "$pd_resolution" ]] && json_err "$E_VALIDATION_FAILED" "pending-decision-resolve requires --resolution"
|
|
711
|
+
|
|
712
|
+
local pd_file="$COLONY_DATA_DIR/pending-decisions.json"
|
|
713
|
+
|
|
714
|
+
if [[ ! -f "$pd_file" ]]; then
|
|
715
|
+
json_err "$E_RESOURCE_NOT_FOUND" "No pending decisions file found"
|
|
716
|
+
fi
|
|
717
|
+
|
|
718
|
+
# Acquire lock for concurrent access
|
|
719
|
+
if type acquire_lock &>/dev/null; then
|
|
720
|
+
acquire_lock "$pd_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on pending-decisions.json"
|
|
721
|
+
trap 'release_lock 2>/dev/null || true' EXIT # SUPPRESS:OK -- cleanup: lock may not be held
|
|
722
|
+
fi
|
|
723
|
+
|
|
724
|
+
local pd_data
|
|
725
|
+
pd_data=$(cat "$pd_file" 2>/dev/null || echo '{"version":"1.0","decisions":[]}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
726
|
+
|
|
727
|
+
# Check if ID exists
|
|
728
|
+
local pd_exists
|
|
729
|
+
pd_exists=$(echo "$pd_data" | jq --arg id "$pd_id" '[.decisions[] | select(.id == $id)] | length')
|
|
730
|
+
if [[ "$pd_exists" -eq 0 ]]; then
|
|
731
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
732
|
+
json_err "$E_RESOURCE_NOT_FOUND" "Decision not found: $pd_id"
|
|
733
|
+
fi
|
|
734
|
+
|
|
735
|
+
local pd_now
|
|
736
|
+
pd_now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
737
|
+
|
|
738
|
+
local pd_updated
|
|
739
|
+
pd_updated=$(echo "$pd_data" | jq \
|
|
740
|
+
--arg id "$pd_id" \
|
|
741
|
+
--arg resolution "$pd_resolution" \
|
|
742
|
+
--arg resolved_at "$pd_now" \
|
|
743
|
+
'(.decisions[] | select(.id == $id)) |= (. + {resolved: true, resolution: $resolution, resolved_at: $resolved_at})' 2>/dev/null) || {
|
|
744
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
745
|
+
json_err "$E_JSON_INVALID" "Failed to resolve decision in pending-decisions.json"
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
atomic_write "$pd_file" "$pd_updated" || {
|
|
749
|
+
type release_lock &>/dev/null && release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
|
|
750
|
+
json_err "$E_JSON_INVALID" "Failed to write pending-decisions.json"
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
type release_lock &>/dev/null && { release_lock 2>/dev/null || true; trap - EXIT; } # SUPPRESS:OK -- cleanup: lock may not be held
|
|
754
|
+
|
|
755
|
+
json_ok "$(jq -n --arg id "$pd_id" '{resolved: true, id: $id}')"
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
# ============================================================================
|
|
759
|
+
# _autopilot_headless_check
|
|
760
|
+
# Check whether headless mode is active in run-state.json
|
|
761
|
+
# Usage: autopilot-headless-check
|
|
762
|
+
# Returns: {"ok":true,"result":{"headless":true|false}}
|
|
763
|
+
# ============================================================================
|
|
764
|
+
_autopilot_headless_check() {
|
|
765
|
+
local ah_state_file="$COLONY_DATA_DIR/run-state.json"
|
|
766
|
+
|
|
767
|
+
if [[ ! -f "$ah_state_file" ]]; then
|
|
768
|
+
json_ok '{"headless":false}'
|
|
769
|
+
exit 0
|
|
770
|
+
fi
|
|
771
|
+
|
|
772
|
+
local ah_headless
|
|
773
|
+
ah_headless=$(jq -r '.headless // false' "$ah_state_file" 2>/dev/null || echo "false") # SUPPRESS:OK -- read-default: field may not exist
|
|
774
|
+
|
|
775
|
+
# Normalize to boolean
|
|
776
|
+
if [[ "$ah_headless" == "true" ]]; then
|
|
777
|
+
json_ok '{"headless":true}'
|
|
778
|
+
else
|
|
779
|
+
json_ok '{"headless":false}'
|
|
780
|
+
fi
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
# ============================================================================
|
|
784
|
+
# _autopilot_set_headless
|
|
785
|
+
# Set the headless flag in run-state.json
|
|
786
|
+
# Usage: autopilot-set-headless <true|false>
|
|
787
|
+
# Returns: {"ok":true,"result":{"headless":true|false,"updated":true}}
|
|
788
|
+
# ============================================================================
|
|
789
|
+
_autopilot_set_headless() {
|
|
790
|
+
local ah_value="${1:-}"
|
|
791
|
+
|
|
792
|
+
if [[ "$ah_value" != "true" && "$ah_value" != "false" ]]; then
|
|
793
|
+
json_err "$E_VALIDATION_FAILED" "autopilot-set-headless requires true or false argument"
|
|
794
|
+
fi
|
|
795
|
+
|
|
796
|
+
local ah_state_file="$COLONY_DATA_DIR/run-state.json"
|
|
797
|
+
|
|
798
|
+
if [[ ! -f "$ah_state_file" ]]; then
|
|
799
|
+
json_err "$E_FILE_NOT_FOUND" "run-state.json not found — autopilot not active"
|
|
800
|
+
fi
|
|
801
|
+
|
|
802
|
+
local ah_headless_bool
|
|
803
|
+
[[ "$ah_value" == "true" ]] && ah_headless_bool=true || ah_headless_bool=false
|
|
804
|
+
|
|
805
|
+
local ah_current ah_updated
|
|
806
|
+
ah_current=$(cat "$ah_state_file" 2>/dev/null || echo '{}') # SUPPRESS:OK -- read-default: file may not exist yet
|
|
807
|
+
ah_updated=$(echo "$ah_current" | jq --argjson headless "$ah_headless_bool" '.headless = $headless' 2>/dev/null) || {
|
|
808
|
+
json_err "$E_JSON_INVALID" "Failed to update headless flag in run-state.json"
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
atomic_write "$ah_state_file" "$ah_updated" || {
|
|
812
|
+
json_err "$E_JSON_INVALID" "Failed to write run-state.json"
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
json_ok "$(jq -n --argjson headless "$ah_headless_bool" '{headless: $headless, updated: true}')"
|
|
816
|
+
}
|