aether-colony 3.1.4 → 3.1.15
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/.claude/commands/ant/archaeology.md +12 -0
- package/.claude/commands/ant/build.md +382 -319
- package/.claude/commands/ant/chaos.md +23 -1
- package/.claude/commands/ant/colonize.md +147 -87
- package/.claude/commands/ant/continue.md +213 -23
- package/.claude/commands/ant/council.md +22 -0
- package/.claude/commands/ant/dream.md +18 -0
- package/.claude/commands/ant/entomb.md +178 -6
- package/.claude/commands/ant/init.md +87 -13
- package/.claude/commands/ant/lay-eggs.md +45 -5
- package/.claude/commands/ant/oracle.md +82 -9
- package/.claude/commands/ant/organize.md +2 -2
- package/.claude/commands/ant/pause-colony.md +86 -28
- package/.claude/commands/ant/phase.md +26 -0
- package/.claude/commands/ant/plan.md +204 -111
- package/.claude/commands/ant/resume-colony.md +23 -1
- package/.claude/commands/ant/resume.md +159 -0
- package/.claude/commands/ant/seal.md +177 -3
- package/.claude/commands/ant/swarm.md +78 -97
- package/.claude/commands/ant/verify-castes.md +7 -7
- package/.claude/commands/ant/watch.md +17 -0
- package/.opencode/agents/aether-ambassador.md +97 -0
- package/.opencode/agents/aether-archaeologist.md +91 -0
- package/.opencode/agents/aether-architect.md +66 -0
- package/.opencode/agents/aether-auditor.md +111 -0
- package/.opencode/agents/aether-builder.md +28 -10
- package/.opencode/agents/aether-chaos.md +98 -0
- package/.opencode/agents/aether-chronicler.md +80 -0
- package/.opencode/agents/aether-gatekeeper.md +107 -0
- package/.opencode/agents/aether-guardian.md +107 -0
- package/.opencode/agents/aether-includer.md +108 -0
- package/.opencode/agents/aether-keeper.md +106 -0
- package/.opencode/agents/aether-measurer.md +119 -0
- package/.opencode/agents/aether-probe.md +91 -0
- package/.opencode/agents/aether-queen.md +72 -19
- package/.opencode/agents/aether-route-setter.md +85 -0
- package/.opencode/agents/aether-sage.md +98 -0
- package/.opencode/agents/aether-scout.md +33 -15
- package/.opencode/agents/aether-surveyor-disciplines.md +334 -0
- package/.opencode/agents/aether-surveyor-nest.md +272 -0
- package/.opencode/agents/aether-surveyor-pathogens.md +209 -0
- package/.opencode/agents/aether-surveyor-provisions.md +277 -0
- package/.opencode/agents/aether-tracker.md +91 -0
- package/.opencode/agents/aether-watcher.md +30 -12
- package/.opencode/agents/aether-weaver.md +87 -0
- package/.opencode/agents/workers.md +1034 -0
- package/.opencode/commands/ant/archaeology.md +44 -26
- package/.opencode/commands/ant/build.md +327 -295
- package/.opencode/commands/ant/chaos.md +32 -4
- package/.opencode/commands/ant/colonize.md +119 -93
- package/.opencode/commands/ant/continue.md +98 -10
- package/.opencode/commands/ant/council.md +28 -0
- package/.opencode/commands/ant/dream.md +24 -0
- package/.opencode/commands/ant/entomb.md +73 -1
- package/.opencode/commands/ant/feedback.md +8 -2
- package/.opencode/commands/ant/flag.md +9 -3
- package/.opencode/commands/ant/flags.md +8 -2
- package/.opencode/commands/ant/focus.md +8 -2
- package/.opencode/commands/ant/help.md +12 -0
- package/.opencode/commands/ant/init.md +49 -4
- package/.opencode/commands/ant/lay-eggs.md +30 -2
- package/.opencode/commands/ant/oracle.md +39 -7
- package/.opencode/commands/ant/organize.md +9 -3
- package/.opencode/commands/ant/pause-colony.md +54 -1
- package/.opencode/commands/ant/phase.md +36 -4
- package/.opencode/commands/ant/plan.md +225 -117
- package/.opencode/commands/ant/redirect.md +8 -2
- package/.opencode/commands/ant/resume-colony.md +51 -26
- package/.opencode/commands/ant/seal.md +76 -0
- package/.opencode/commands/ant/status.md +50 -20
- package/.opencode/commands/ant/swarm.md +108 -104
- package/.opencode/commands/ant/tunnels.md +107 -2
- package/CHANGELOG.md +21 -0
- package/README.md +199 -86
- package/bin/cli.js +142 -25
- package/bin/generate-commands.sh +100 -16
- package/bin/lib/caste-colors.js +5 -5
- package/bin/lib/errors.js +16 -0
- package/bin/lib/file-lock.js +279 -44
- package/bin/lib/state-sync.js +206 -23
- package/bin/lib/update-transaction.js +206 -24
- package/bin/sync-to-runtime.sh +129 -0
- package/package.json +2 -2
- package/runtime/CONTEXT.md +160 -0
- package/runtime/aether-utils.sh +1421 -55
- package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +1343 -0
- package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +2642 -0
- package/runtime/docs/PHEROMONE-INJECTION.md +240 -0
- package/runtime/docs/PHEROMONE-INTEGRATION.md +192 -0
- package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +426 -0
- package/runtime/docs/README.md +94 -0
- package/runtime/docs/VISUAL-OUTPUT-SPEC.md +219 -0
- package/runtime/docs/biological-reference.md +272 -0
- package/runtime/docs/codebase-review.md +399 -0
- package/runtime/docs/command-sync.md +164 -0
- package/runtime/docs/implementation-learnings.md +89 -0
- package/runtime/docs/known-issues.md +217 -0
- package/runtime/docs/namespace.md +148 -0
- package/runtime/docs/planning-discipline.md +159 -0
- package/runtime/lib/queen-utils.sh +729 -0
- package/runtime/model-profiles.yaml +100 -0
- package/runtime/recover.sh +136 -0
- package/runtime/templates/QUEEN.md.template +79 -0
- package/runtime/utils/atomic-write.sh +5 -5
- package/runtime/utils/chamber-utils.sh +6 -3
- package/runtime/utils/error-handler.sh +200 -0
- package/runtime/utils/queen-to-md.xsl +395 -0
- package/runtime/utils/spawn-tree.sh +428 -0
- package/runtime/utils/spawn-with-model.sh +56 -0
- package/runtime/utils/state-loader.sh +215 -0
- package/runtime/utils/swarm-display.sh +5 -5
- package/runtime/utils/watch-spawn-tree.sh +90 -22
- package/runtime/utils/xml-compose.sh +247 -0
- package/runtime/utils/xml-core.sh +186 -0
- package/runtime/utils/xml-utils.sh +2161 -0
- package/runtime/verification-loop.md +1 -1
- package/runtime/workers-new-castes.md +516 -0
- package/runtime/workers.md +20 -8
- package/.aether/visualizations/anthill-stages/brood-stable.txt +0 -26
- package/.aether/visualizations/anthill-stages/crowned-anthill.txt +0 -30
- package/.aether/visualizations/anthill-stages/first-mound.txt +0 -18
- package/.aether/visualizations/anthill-stages/open-chambers.txt +0 -24
- package/.aether/visualizations/anthill-stages/sealed-chambers.txt +0 -28
- package/.aether/visualizations/anthill-stages/ventilated-nest.txt +0 -27
package/runtime/aether-utils.sh
CHANGED
|
@@ -27,6 +27,20 @@ CURRENT_LOCK=${CURRENT_LOCK:-""}
|
|
|
27
27
|
[[ -f "$SCRIPT_DIR/utils/atomic-write.sh" ]] && source "$SCRIPT_DIR/utils/atomic-write.sh"
|
|
28
28
|
[[ -f "$SCRIPT_DIR/utils/error-handler.sh" ]] && source "$SCRIPT_DIR/utils/error-handler.sh"
|
|
29
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}"
|
|
30
44
|
|
|
31
45
|
# Feature detection for graceful degradation
|
|
32
46
|
# These checks run silently - failures are logged but don't block operation
|
|
@@ -74,20 +88,379 @@ fi
|
|
|
74
88
|
# --- Caste emoji helper ---
|
|
75
89
|
get_caste_emoji() {
|
|
76
90
|
case "$1" in
|
|
77
|
-
*Queen*|*QUEEN*|*queen*) echo "
|
|
78
|
-
*Builder*|*builder*|*Bolt*|*Hammer*|*Forge*|*Mason*|*Brick*|*Anvil*|*Weld*) echo "
|
|
79
|
-
*Watcher*|*watcher*|*Vigil*|*Sentinel*|*Guard*|*Keen*|*Sharp*|*Hawk*|*Alert*) echo "
|
|
80
|
-
*Scout*|*scout*|*Swift*|*Dash*|*Ranger*|*Track*|*Seek*|*Path*|*Roam*|*Quest*) echo "
|
|
81
|
-
*Colonizer*|*colonizer*|*Pioneer*|*Map*|*Chart*|*Venture*|*Explore*|*Compass*|*Atlas*|*Trek*) echo "
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
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 "🔄🐜" ;;
|
|
87
113
|
*) echo "🐜" ;;
|
|
88
114
|
esac
|
|
89
115
|
}
|
|
90
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
|
+
|
|
91
464
|
# --- Subcommand dispatch ---
|
|
92
465
|
cmd="${1:-help}"
|
|
93
466
|
shift 2>/dev/null || true
|
|
@@ -95,7 +468,7 @@ shift 2>/dev/null || true
|
|
|
95
468
|
case "$cmd" in
|
|
96
469
|
help)
|
|
97
470
|
cat <<'EOF'
|
|
98
|
-
{"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"],"description":"Aether Colony Utility Layer — deterministic ops for the ant colony"}
|
|
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"}
|
|
99
472
|
EOF
|
|
100
473
|
;;
|
|
101
474
|
version)
|
|
@@ -253,7 +626,7 @@ EOF
|
|
|
253
626
|
json_ok "$(echo "$content" | jq -Rs '.')"
|
|
254
627
|
;;
|
|
255
628
|
learning-promote)
|
|
256
|
-
[[ $# -ge 3 ]] || json_err "Usage: learning-promote <content> <source_project> <source_phase> [tags]"
|
|
629
|
+
[[ $# -ge 3 ]] || json_err "$E_VALIDATION_FAILED" "Usage: learning-promote <content> <source_project> <source_phase> [tags]"
|
|
257
630
|
content="$1"
|
|
258
631
|
source_project="$2"
|
|
259
632
|
source_phase="$3"
|
|
@@ -291,13 +664,13 @@ EOF
|
|
|
291
664
|
tags: $tags,
|
|
292
665
|
promoted_at: $ts
|
|
293
666
|
}]
|
|
294
|
-
' "$global_file") || json_err "Failed to update learnings.json"
|
|
667
|
+
' "$global_file") || json_err "$E_JSON_INVALID" "Failed to update learnings.json"
|
|
295
668
|
|
|
296
669
|
echo "$updated" > "$global_file"
|
|
297
670
|
json_ok "{\"promoted\":true,\"id\":\"$id\",\"count\":$((current_count + 1)),\"cap\":50}"
|
|
298
671
|
;;
|
|
299
672
|
learning-inject)
|
|
300
|
-
[[ $# -ge 1 ]] || json_err "Usage: learning-inject <tech_keywords_csv>"
|
|
673
|
+
[[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: learning-inject <tech_keywords_csv>"
|
|
301
674
|
keywords="$1"
|
|
302
675
|
|
|
303
676
|
global_file="$DATA_DIR/learnings.json"
|
|
@@ -325,7 +698,7 @@ EOF
|
|
|
325
698
|
task_summary="${4:-}"
|
|
326
699
|
model="${5:-default}"
|
|
327
700
|
status="${6:-spawned}"
|
|
328
|
-
[[ -z "$parent_id" || -z "$child_caste" || -z "$task_summary" ]] && json_err "Usage: spawn-log <parent_id> <child_caste> <child_name> <task_summary> [model] [status]"
|
|
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]"
|
|
329
702
|
mkdir -p "$DATA_DIR"
|
|
330
703
|
ts=$(date -u +"%H:%M:%S")
|
|
331
704
|
ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
@@ -335,14 +708,15 @@ EOF
|
|
|
335
708
|
echo "[$ts] ⚡ SPAWN $parent_emoji $parent_id -> $emoji $child_name ($child_caste): $task_summary [model: $model]" >> "$DATA_DIR/activity.log"
|
|
336
709
|
# Log to spawn tree file for visualization (NEW FORMAT: includes model field)
|
|
337
710
|
echo "$ts_full|$parent_id|$child_caste|$child_name|$task_summary|$model|$status" >> "$DATA_DIR/spawn-tree.txt"
|
|
338
|
-
|
|
711
|
+
# Return emoji-formatted result for display
|
|
712
|
+
json_ok "\"⚡ $emoji $child_name spawned\""
|
|
339
713
|
;;
|
|
340
714
|
spawn-complete)
|
|
341
715
|
# Usage: spawn-complete <ant_name> <status> [summary]
|
|
342
716
|
ant_name="${1:-}"
|
|
343
717
|
status="${2:-completed}"
|
|
344
718
|
summary="${3:-}"
|
|
345
|
-
[[ -z "$ant_name" ]] && json_err "Usage: spawn-complete <ant_name> <status> [summary]"
|
|
719
|
+
[[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-complete <ant_name> <status> [summary]"
|
|
346
720
|
mkdir -p "$DATA_DIR"
|
|
347
721
|
ts=$(date -u +"%H:%M:%S")
|
|
348
722
|
ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
@@ -353,7 +727,8 @@ EOF
|
|
|
353
727
|
echo "[$ts] $status_icon $emoji $ant_name: $status${summary:+ - $summary}" >> "$DATA_DIR/activity.log"
|
|
354
728
|
# Update spawn tree
|
|
355
729
|
echo "$ts_full|$ant_name|$status|$summary" >> "$DATA_DIR/spawn-tree.txt"
|
|
356
|
-
|
|
730
|
+
# Return emoji-formatted result for display
|
|
731
|
+
json_ok "\"$status_icon $emoji $ant_name: ${summary:-$status}\""
|
|
357
732
|
;;
|
|
358
733
|
spawn-can-spawn)
|
|
359
734
|
# Check if spawning is allowed at given depth
|
|
@@ -492,7 +867,7 @@ EOF
|
|
|
492
867
|
pattern_name="${1:-}"
|
|
493
868
|
description="${2:-}"
|
|
494
869
|
severity="${3:-warning}"
|
|
495
|
-
[[ -z "$pattern_name" || -z "$description" ]] && json_err "Usage: error-flag-pattern <pattern_name> <description> [severity]"
|
|
870
|
+
[[ -z "$pattern_name" || -z "$description" ]] && json_err "$E_VALIDATION_FAILED" "Usage: error-flag-pattern <pattern_name> <description> [severity]"
|
|
496
871
|
|
|
497
872
|
patterns_file="$DATA_DIR/error-patterns.json"
|
|
498
873
|
mkdir -p "$DATA_DIR"
|
|
@@ -515,7 +890,7 @@ EOF
|
|
|
515
890
|
.last_seen = $ts |
|
|
516
891
|
.projects = ((.projects + [$proj]) | unique)
|
|
517
892
|
else . end]
|
|
518
|
-
' "$patterns_file") || json_err "Failed to update pattern"
|
|
893
|
+
' "$patterns_file") || json_err "$E_JSON_INVALID" "Failed to update pattern"
|
|
519
894
|
echo "$updated" > "$patterns_file"
|
|
520
895
|
count=$(echo "$updated" | jq --arg name "$pattern_name" '.patterns[] | select(.name == $name) | .occurrences')
|
|
521
896
|
json_ok "{\"updated\":true,\"pattern\":\"$pattern_name\",\"occurrences\":$count}"
|
|
@@ -532,7 +907,7 @@ EOF
|
|
|
532
907
|
"projects": [$proj],
|
|
533
908
|
"resolved": false
|
|
534
909
|
}]
|
|
535
|
-
' "$patterns_file") || json_err "Failed to add pattern"
|
|
910
|
+
' "$patterns_file") || json_err "$E_JSON_INVALID" "Failed to add pattern"
|
|
536
911
|
echo "$updated" > "$patterns_file"
|
|
537
912
|
json_ok "{\"created\":true,\"pattern\":\"$pattern_name\"}"
|
|
538
913
|
fi
|
|
@@ -557,7 +932,7 @@ EOF
|
|
|
557
932
|
# Usage: check-antipattern <file_path>
|
|
558
933
|
# Returns JSON with critical issues and warnings
|
|
559
934
|
file_path="${1:-}"
|
|
560
|
-
[[ -z "$file_path" ]] && json_err "Usage: check-antipattern <file_path>"
|
|
935
|
+
[[ -z "$file_path" ]] && json_err "$E_VALIDATION_FAILED" "Usage: check-antipattern <file_path>"
|
|
561
936
|
[[ ! -f "$file_path" ]] && json_ok '{"critical":[],"warnings":[],"clean":true}'
|
|
562
937
|
|
|
563
938
|
criticals=()
|
|
@@ -632,7 +1007,7 @@ EOF
|
|
|
632
1007
|
# Exit code 0 if no match, 1 if match found
|
|
633
1008
|
target_file="${1:-}"
|
|
634
1009
|
signature_name="${2:-}"
|
|
635
|
-
[[ -z "$target_file" || -z "$signature_name" ]] && json_err "Usage: signature-scan <target_file> <signature_name>"
|
|
1010
|
+
[[ -z "$target_file" || -z "$signature_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: signature-scan <target_file> <signature_name>"
|
|
636
1011
|
|
|
637
1012
|
# Handle missing target file gracefully
|
|
638
1013
|
if [[ ! -f "$target_file" ]]; then
|
|
@@ -687,14 +1062,14 @@ EOF
|
|
|
687
1062
|
if [[ -z "$file_pattern" ]]; then
|
|
688
1063
|
file_pattern="*"
|
|
689
1064
|
fi
|
|
690
|
-
[[ -z "$target_dir" ]] && json_err "Usage: signature-match <directory> [file_pattern]"
|
|
1065
|
+
[[ -z "$target_dir" ]] && json_err "$E_VALIDATION_FAILED" "Usage: signature-match <directory> [file_pattern]"
|
|
691
1066
|
|
|
692
1067
|
# Validate directory exists
|
|
693
|
-
[[ ! -d "$target_dir" ]] && json_err "Directory not found: $target_dir"
|
|
1068
|
+
[[ ! -d "$target_dir" ]] && json_err "$E_FILE_NOT_FOUND" "Directory not found: $target_dir"
|
|
694
1069
|
|
|
695
1070
|
# Path to signatures file
|
|
696
1071
|
signatures_file="$DATA_DIR/signatures.json"
|
|
697
|
-
[[ ! -f "$signatures_file" ]] && json_err "Signatures file not found"
|
|
1072
|
+
[[ ! -f "$signatures_file" ]] && json_err "$E_FILE_NOT_FOUND" "Signatures file not found"
|
|
698
1073
|
|
|
699
1074
|
# Read high-confidence signatures (confidence >= 0.7) using jq -c for compact single-line output
|
|
700
1075
|
high_conf_signatures=$(jq -c '.signatures[] | select(.confidence_threshold >= 0.7)' "$signatures_file" 2>/dev/null)
|
|
@@ -784,7 +1159,7 @@ EOF
|
|
|
784
1159
|
desc="${3:-}"
|
|
785
1160
|
source="${4:-manual}"
|
|
786
1161
|
phase="${5:-null}"
|
|
787
|
-
[[ -z "$title" ]] && json_err "Usage: flag-add <type> <title> <description> [source] [phase]"
|
|
1162
|
+
[[ -z "$title" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-add <type> <title> <description> [source] [phase]"
|
|
788
1163
|
|
|
789
1164
|
mkdir -p "$DATA_DIR"
|
|
790
1165
|
flags_file="$DATA_DIR/flags.json"
|
|
@@ -797,6 +1172,7 @@ EOF
|
|
|
797
1172
|
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
798
1173
|
|
|
799
1174
|
# Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
|
|
1175
|
+
lock_acquired=false
|
|
800
1176
|
if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
|
|
801
1177
|
json_warn "W_DEGRADED" "File locking disabled - proceeding without lock: $(type _feature_reason &>/dev/null && _feature_reason file_locking || echo 'unknown')"
|
|
802
1178
|
else
|
|
@@ -808,6 +1184,9 @@ EOF
|
|
|
808
1184
|
exit 1
|
|
809
1185
|
fi
|
|
810
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
|
|
811
1190
|
fi
|
|
812
1191
|
|
|
813
1192
|
# Map type to severity
|
|
@@ -842,10 +1221,11 @@ EOF
|
|
|
842
1221
|
resolution: null,
|
|
843
1222
|
auto_resolve_on: (if $type == "blocker" and ($source | test("chaos") | not) then "build_pass" else null end)
|
|
844
1223
|
}]
|
|
845
|
-
' "$flags_file") || {
|
|
1224
|
+
' "$flags_file") || { json_err "$E_JSON_INVALID" "Failed to add flag"; }
|
|
846
1225
|
|
|
847
1226
|
atomic_write "$flags_file" "$updated"
|
|
848
|
-
|
|
1227
|
+
# Lock released by trap on exit (BUG-002 fix)
|
|
1228
|
+
trap - EXIT
|
|
849
1229
|
json_ok "{\"id\":\"$id\",\"type\":\"$type\",\"severity\":\"$severity\"}"
|
|
850
1230
|
;;
|
|
851
1231
|
flag-check-blockers)
|
|
@@ -882,10 +1262,10 @@ EOF
|
|
|
882
1262
|
# Usage: flag-resolve <flag_id> [resolution_message]
|
|
883
1263
|
flag_id="${1:-}"
|
|
884
1264
|
resolution="${2:-Resolved}"
|
|
885
|
-
[[ -z "$flag_id" ]] && json_err "Usage: flag-resolve <flag_id> [resolution_message]"
|
|
1265
|
+
[[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-resolve <flag_id> [resolution_message]"
|
|
886
1266
|
|
|
887
1267
|
flags_file="$DATA_DIR/flags.json"
|
|
888
|
-
[[ ! -f "$flags_file" ]] && json_err "No flags file found"
|
|
1268
|
+
[[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
|
|
889
1269
|
|
|
890
1270
|
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
891
1271
|
|
|
@@ -916,10 +1296,10 @@ EOF
|
|
|
916
1296
|
# Acknowledge a flag (issue continues but noted)
|
|
917
1297
|
# Usage: flag-acknowledge <flag_id>
|
|
918
1298
|
flag_id="${1:-}"
|
|
919
|
-
[[ -z "$flag_id" ]] && json_err "Usage: flag-acknowledge <flag_id>"
|
|
1299
|
+
[[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-acknowledge <flag_id>"
|
|
920
1300
|
|
|
921
1301
|
flags_file="$DATA_DIR/flags.json"
|
|
922
|
-
[[ ! -f "$flags_file" ]] && json_err "No flags file found"
|
|
1302
|
+
[[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
|
|
923
1303
|
|
|
924
1304
|
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
925
1305
|
|
|
@@ -996,16 +1376,22 @@ EOF
|
|
|
996
1376
|
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
997
1377
|
|
|
998
1378
|
# Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
|
|
1379
|
+
lock_acquired=false
|
|
999
1380
|
if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
|
|
1000
1381
|
json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
|
|
1001
1382
|
else
|
|
1002
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
|
|
1003
1387
|
fi
|
|
1004
1388
|
|
|
1005
1389
|
# Count how many will be resolved
|
|
1006
1390
|
count=$(jq --arg trigger "$trigger" '
|
|
1007
1391
|
[.flags[] | select(.auto_resolve_on == $trigger and .resolved_at == null)] | length
|
|
1008
|
-
' "$flags_file")
|
|
1392
|
+
' "$flags_file") || {
|
|
1393
|
+
json_err "$E_JSON_INVALID" "Failed to count flags for auto-resolve"
|
|
1394
|
+
}
|
|
1009
1395
|
|
|
1010
1396
|
# Resolve them
|
|
1011
1397
|
updated=$(jq --arg trigger "$trigger" --arg ts "$ts" '
|
|
@@ -1013,10 +1399,15 @@ EOF
|
|
|
1013
1399
|
.resolved_at = $ts |
|
|
1014
1400
|
.resolution = "Auto-resolved on " + $trigger
|
|
1015
1401
|
else . end]
|
|
1016
|
-
' "$flags_file")
|
|
1402
|
+
' "$flags_file") || {
|
|
1403
|
+
json_err "$E_JSON_INVALID" "Failed to auto-resolve flags"
|
|
1404
|
+
}
|
|
1017
1405
|
|
|
1018
1406
|
atomic_write "$flags_file" "$updated"
|
|
1019
|
-
|
|
1407
|
+
# Lock released by trap on exit (BUG-005/BUG-011 fix)
|
|
1408
|
+
if [[ "$lock_acquired" == "true" ]]; then
|
|
1409
|
+
trap - EXIT
|
|
1410
|
+
fi
|
|
1020
1411
|
json_ok "{\"resolved\":$count,\"trigger\":\"$trigger\"}"
|
|
1021
1412
|
;;
|
|
1022
1413
|
generate-ant-name)
|
|
@@ -1032,6 +1423,17 @@ EOF
|
|
|
1032
1423
|
chaos) prefixes=("Probe" "Stress" "Shake" "Twist" "Snap" "Breach" "Surge" "Jolt") ;;
|
|
1033
1424
|
archaeologist) prefixes=("Relic" "Fossil" "Dig" "Shard" "Epoch" "Strata" "Lore" "Glyph") ;;
|
|
1034
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") ;;
|
|
1035
1437
|
*) prefixes=("Ant" "Worker" "Drone" "Toiler" "Marcher" "Runner" "Carrier" "Helper") ;;
|
|
1036
1438
|
esac
|
|
1037
1439
|
# Pick random prefix and add random number
|
|
@@ -1174,10 +1576,10 @@ EOF
|
|
|
1174
1576
|
confidence="${3:-0.5}"
|
|
1175
1577
|
finding="${4:-}"
|
|
1176
1578
|
|
|
1177
|
-
[[ -z "$swarm_id" || -z "$scout_type" || -z "$finding" ]] && json_err "Usage: swarm-findings-add <swarm_id> <scout_type> <confidence> <finding_json>"
|
|
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>"
|
|
1178
1580
|
|
|
1179
1581
|
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1180
|
-
[[ ! -f "$findings_file" ]] && json_err "Swarm findings file not found: $swarm_id"
|
|
1582
|
+
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
1181
1583
|
|
|
1182
1584
|
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1183
1585
|
|
|
@@ -1200,10 +1602,10 @@ EOF
|
|
|
1200
1602
|
# Read all findings for a swarm
|
|
1201
1603
|
# Usage: swarm-findings-read <swarm_id>
|
|
1202
1604
|
swarm_id="${1:-}"
|
|
1203
|
-
[[ -z "$swarm_id" ]] && json_err "Usage: swarm-findings-read <swarm_id>"
|
|
1605
|
+
[[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-read <swarm_id>"
|
|
1204
1606
|
|
|
1205
1607
|
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1206
|
-
[[ ! -f "$findings_file" ]] && json_err "Swarm findings file not found: $swarm_id"
|
|
1608
|
+
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
1207
1609
|
|
|
1208
1610
|
json_ok "$(cat "$findings_file")"
|
|
1209
1611
|
;;
|
|
@@ -1214,10 +1616,10 @@ EOF
|
|
|
1214
1616
|
swarm_id="${1:-}"
|
|
1215
1617
|
solution="${2:-}"
|
|
1216
1618
|
|
|
1217
|
-
[[ -z "$swarm_id" || -z "$solution" ]] && json_err "Usage: swarm-solution-set <swarm_id> <solution_json>"
|
|
1619
|
+
[[ -z "$swarm_id" || -z "$solution" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-solution-set <swarm_id> <solution_json>"
|
|
1218
1620
|
|
|
1219
1621
|
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1220
|
-
[[ ! -f "$findings_file" ]] && json_err "Swarm findings file not found: $swarm_id"
|
|
1622
|
+
[[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
|
|
1221
1623
|
|
|
1222
1624
|
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
1223
1625
|
|
|
@@ -1237,7 +1639,7 @@ EOF
|
|
|
1237
1639
|
swarm_id="${1:-}"
|
|
1238
1640
|
archive="${2:-}"
|
|
1239
1641
|
|
|
1240
|
-
[[ -z "$swarm_id" ]] && json_err "Usage: swarm-cleanup <swarm_id> [--archive]"
|
|
1642
|
+
[[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-cleanup <swarm_id> [--archive]"
|
|
1241
1643
|
|
|
1242
1644
|
findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
|
|
1243
1645
|
|
|
@@ -1258,7 +1660,7 @@ EOF
|
|
|
1258
1660
|
grave-add)
|
|
1259
1661
|
# Record a grave marker when a builder fails at a file
|
|
1260
1662
|
# Usage: grave-add <file> <ant_name> <task_id> <phase> <failure_summary> [function] [line]
|
|
1261
|
-
[[ $# -ge 5 ]] || json_err "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]"
|
|
1262
1664
|
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
1263
1665
|
file="$1"
|
|
1264
1666
|
ant_name="$2"
|
|
@@ -1309,7 +1711,7 @@ EOF
|
|
|
1309
1711
|
# Query for grave markers near a file path
|
|
1310
1712
|
# Usage: grave-check <file_path>
|
|
1311
1713
|
# Read-only, never modifies state
|
|
1312
|
-
[[ $# -ge 1 ]] || json_err "Usage: grave-check <file_path>"
|
|
1714
|
+
[[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: grave-check <file_path>"
|
|
1313
1715
|
[[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
|
|
1314
1716
|
check_file="$1"
|
|
1315
1717
|
check_dir=$(dirname "$check_file")
|
|
@@ -1333,14 +1735,15 @@ EOF
|
|
|
1333
1735
|
|
|
1334
1736
|
generate-commit-message)
|
|
1335
1737
|
# Generate an intelligent commit message from colony context
|
|
1336
|
-
# Usage: generate-commit-message <type> <phase_id> <phase_name> [summary]
|
|
1337
|
-
# Types: "milestone" | "pause" | "fix"
|
|
1338
|
-
# Returns: {"message": "...", "body": "...", "files_changed": N}
|
|
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, ...}
|
|
1339
1741
|
|
|
1340
1742
|
msg_type="${1:-milestone}"
|
|
1341
1743
|
phase_id="${2:-0}"
|
|
1342
1744
|
phase_name="${3:-unknown}"
|
|
1343
|
-
summary="${4:-}"
|
|
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")
|
|
1344
1747
|
|
|
1345
1748
|
# Count changed files
|
|
1346
1749
|
files_changed=0
|
|
@@ -1373,6 +1776,33 @@ EOF
|
|
|
1373
1776
|
fi
|
|
1374
1777
|
body="Swarm-verified fix applied and tested."
|
|
1375
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
|
+
;;
|
|
1376
1806
|
*)
|
|
1377
1807
|
message="aether-checkpoint: phase ${phase_id}"
|
|
1378
1808
|
body=""
|
|
@@ -1387,6 +1817,32 @@ EOF
|
|
|
1387
1817
|
json_ok "{\"message\":\"$message\",\"body\":\"$body\",\"files_changed\":$files_changed}"
|
|
1388
1818
|
;;
|
|
1389
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
|
+
|
|
1390
1846
|
# ============================================
|
|
1391
1847
|
# REGISTRY & UPDATE UTILITIES
|
|
1392
1848
|
# ============================================
|
|
@@ -1409,7 +1865,8 @@ EOF
|
|
|
1409
1865
|
if [[ "$local_ver" == "$hub_ver" ]]; then
|
|
1410
1866
|
json_ok '""'
|
|
1411
1867
|
else
|
|
1412
|
-
|
|
1868
|
+
printf -v msg 'Update available: %s to %s (run /ant:update)' "$local_ver" "$hub_ver"
|
|
1869
|
+
json_ok "$msg"
|
|
1413
1870
|
fi
|
|
1414
1871
|
;;
|
|
1415
1872
|
|
|
@@ -1418,7 +1875,7 @@ EOF
|
|
|
1418
1875
|
# Usage: registry-add <repo_path> <version>
|
|
1419
1876
|
repo_path="${1:-}"
|
|
1420
1877
|
repo_version="${2:-}"
|
|
1421
|
-
[[ -z "$repo_path" || -z "$repo_version" ]] && json_err "Usage: registry-add <repo_path> <version>"
|
|
1878
|
+
[[ -z "$repo_path" || -z "$repo_version" ]] && json_err "$E_VALIDATION_FAILED" "Usage: registry-add <repo_path> <version>"
|
|
1422
1879
|
|
|
1423
1880
|
registry_file="$HOME/.aether/registry.json"
|
|
1424
1881
|
mkdir -p "$HOME/.aether"
|
|
@@ -1439,7 +1896,7 @@ EOF
|
|
|
1439
1896
|
.version = $ver |
|
|
1440
1897
|
.updated_at = $ts
|
|
1441
1898
|
else . end]
|
|
1442
|
-
' "$registry_file") || json_err "Failed to update registry"
|
|
1899
|
+
' "$registry_file") || json_err "$E_JSON_INVALID" "Failed to update registry"
|
|
1443
1900
|
else
|
|
1444
1901
|
# Add new entry
|
|
1445
1902
|
updated=$(jq --arg path "$repo_path" --arg ver "$repo_version" --arg ts "$ts" '
|
|
@@ -1449,7 +1906,7 @@ EOF
|
|
|
1449
1906
|
"registered_at": $ts,
|
|
1450
1907
|
"updated_at": $ts
|
|
1451
1908
|
}]
|
|
1452
|
-
' "$registry_file") || json_err "Failed to update registry"
|
|
1909
|
+
' "$registry_file") || json_err "$E_JSON_INVALID" "Failed to update registry"
|
|
1453
1910
|
fi
|
|
1454
1911
|
|
|
1455
1912
|
echo "$updated" > "$registry_file"
|
|
@@ -1462,7 +1919,7 @@ EOF
|
|
|
1462
1919
|
hub_system="$HOME/.aether/system"
|
|
1463
1920
|
local_aether="$AETHER_ROOT/.aether"
|
|
1464
1921
|
|
|
1465
|
-
[[ ! -d "$hub_system" ]] && json_err "Hub system directory not found: $hub_system"
|
|
1922
|
+
[[ ! -d "$hub_system" ]] && json_err "$E_HUB_NOT_FOUND" "Hub system directory not found: $hub_system"
|
|
1466
1923
|
|
|
1467
1924
|
# Allowlist of system files to copy (relative to system/)
|
|
1468
1925
|
allowlist=(
|
|
@@ -2236,7 +2693,916 @@ NODESCRIPT
|
|
|
2236
2693
|
json_ok "{\"item\":\"$item\",\"state\":\"collapsed\",\"view\":\"$view_name\"}"
|
|
2237
2694
|
;;
|
|
2238
2695
|
|
|
2696
|
+
queen-init)
|
|
2697
|
+
# Initialize QUEEN.md from template
|
|
2698
|
+
# Creates .aether/QUEEN.md from template if missing
|
|
2699
|
+
queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
|
|
2700
|
+
|
|
2701
|
+
# Check multiple locations for template
|
|
2702
|
+
# Order: dev (runtime/) -> npm install (hub) -> legacy
|
|
2703
|
+
template_file=""
|
|
2704
|
+
for path in \
|
|
2705
|
+
"$AETHER_ROOT/runtime/templates/QUEEN.md.template" \
|
|
2706
|
+
"$HOME/.aether/templates/QUEEN.md.template" \
|
|
2707
|
+
"$AETHER_ROOT/.aether/templates/QUEEN.md.template"; do
|
|
2708
|
+
if [[ -f "$path" ]]; then
|
|
2709
|
+
template_file="$path"
|
|
2710
|
+
break
|
|
2711
|
+
fi
|
|
2712
|
+
done
|
|
2713
|
+
|
|
2714
|
+
# Ensure docs directory exists
|
|
2715
|
+
mkdir -p "$AETHER_ROOT/.aether/docs"
|
|
2716
|
+
|
|
2717
|
+
# Check if QUEEN.md already exists and has content
|
|
2718
|
+
if [[ -f "$queen_file" ]] && [[ -s "$queen_file" ]]; then
|
|
2719
|
+
json_ok '{"created":false,"path":".aether/docs/QUEEN.md","reason":"already_exists"}'
|
|
2720
|
+
exit 0
|
|
2721
|
+
fi
|
|
2722
|
+
|
|
2723
|
+
# Check if template was found
|
|
2724
|
+
if [[ -z "$template_file" ]]; then
|
|
2725
|
+
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"]}'
|
|
2726
|
+
exit 1
|
|
2727
|
+
fi
|
|
2728
|
+
|
|
2729
|
+
# Create QUEEN.md from template with timestamp substitution
|
|
2730
|
+
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2731
|
+
sed -e "s/{TIMESTAMP}/$timestamp/g" "$template_file" > "$queen_file"
|
|
2732
|
+
|
|
2733
|
+
if [[ -f "$queen_file" ]]; then
|
|
2734
|
+
json_ok "{\"created\":true,\"path\":\".aether/docs/QUEEN.md\",\"source\":\"$template_file\"}"
|
|
2735
|
+
else
|
|
2736
|
+
json_err "$E_FILE_NOT_FOUND" "Failed to create QUEEN.md" '{"path":".aether/docs/QUEEN.md"}'
|
|
2737
|
+
exit 1
|
|
2738
|
+
fi
|
|
2739
|
+
;;
|
|
2740
|
+
|
|
2741
|
+
queen-read)
|
|
2742
|
+
# Read QUEEN.md and return wisdom as JSON for worker priming
|
|
2743
|
+
# Extracts METADATA block and sections for colony guidance
|
|
2744
|
+
queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
|
|
2745
|
+
|
|
2746
|
+
# Check if QUEEN.md exists
|
|
2747
|
+
if [[ ! -f "$queen_file" ]]; then
|
|
2748
|
+
json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/docs/QUEEN.md"}'
|
|
2749
|
+
exit 1
|
|
2750
|
+
fi
|
|
2751
|
+
|
|
2752
|
+
# Extract METADATA JSON block (between <!-- METADATA and -->)
|
|
2753
|
+
metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
|
|
2754
|
+
|
|
2755
|
+
# If no metadata found, return empty structure
|
|
2756
|
+
if [[ -z "$metadata" ]]; then
|
|
2757
|
+
metadata='{"version":"unknown","last_evolved":null,"colonies_contributed":[],"promotion_thresholds":{},"stats":{}}'
|
|
2758
|
+
fi
|
|
2759
|
+
|
|
2760
|
+
# Extract sections content for worker priming
|
|
2761
|
+
# Use awk to parse markdown sections - remove header line and trailing section header
|
|
2762
|
+
philosophies=$(awk '/^## 📜 Philosophies$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
2763
|
+
patterns=$(awk '/^## 🧭 Patterns$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
2764
|
+
redirects=$(awk '/^## ⚠️ Redirects$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
2765
|
+
stack_wisdom=$(awk '/^## 🔧 Stack Wisdom$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
2766
|
+
decrees=$(awk '/^## 🏛️ Decrees$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
|
|
2767
|
+
|
|
2768
|
+
# Build JSON output
|
|
2769
|
+
result=$(jq -n \
|
|
2770
|
+
--argjson meta "$metadata" \
|
|
2771
|
+
--arg philosophies "$philosophies" \
|
|
2772
|
+
--arg patterns "$patterns" \
|
|
2773
|
+
--arg redirects "$redirects" \
|
|
2774
|
+
--arg stack_wisdom "$stack_wisdom" \
|
|
2775
|
+
--arg decrees "$decrees" \
|
|
2776
|
+
'{
|
|
2777
|
+
metadata: $meta,
|
|
2778
|
+
wisdom: {
|
|
2779
|
+
philosophies: $philosophies,
|
|
2780
|
+
patterns: $patterns,
|
|
2781
|
+
redirects: $redirects,
|
|
2782
|
+
stack_wisdom: $stack_wisdom,
|
|
2783
|
+
decrees: $decrees
|
|
2784
|
+
},
|
|
2785
|
+
priming: {
|
|
2786
|
+
has_philosophies: ($philosophies | length) > 0 and $philosophies != "*No philosophies recorded yet.*\n",
|
|
2787
|
+
has_patterns: ($patterns | length) > 0 and $patterns != "*No patterns recorded yet.*\n",
|
|
2788
|
+
has_redirects: ($redirects | length) > 0 and $redirects != "*No redirects recorded yet.*\n",
|
|
2789
|
+
has_stack_wisdom: ($stack_wisdom | length) > 0 and $stack_wisdom != "*No stack wisdom recorded yet.*\n",
|
|
2790
|
+
has_decrees: ($decrees | length) > 0 and $decrees != "*No decrees recorded yet.*\n"
|
|
2791
|
+
}
|
|
2792
|
+
}')
|
|
2793
|
+
|
|
2794
|
+
json_ok "$result"
|
|
2795
|
+
;;
|
|
2796
|
+
|
|
2797
|
+
queen-promote)
|
|
2798
|
+
# Promote a learning to QUEEN.md wisdom
|
|
2799
|
+
# Usage: queen-promote <type> <content> <colony_name>
|
|
2800
|
+
# Types: philosophy, pattern, redirect, stack, decree
|
|
2801
|
+
wisdom_type="${1:-}"
|
|
2802
|
+
content="${2:-}"
|
|
2803
|
+
colony_name="${3:-}"
|
|
2804
|
+
|
|
2805
|
+
# Validate required arguments
|
|
2806
|
+
[[ -z "$wisdom_type" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"type"}'
|
|
2807
|
+
[[ -z "$content" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"content"}'
|
|
2808
|
+
[[ -z "$colony_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"colony_name"}'
|
|
2809
|
+
|
|
2810
|
+
# Validate type
|
|
2811
|
+
valid_types=("philosophy" "pattern" "redirect" "stack" "decree")
|
|
2812
|
+
type_valid=false
|
|
2813
|
+
for vt in "${valid_types[@]}"; do
|
|
2814
|
+
[[ "$wisdom_type" == "$vt" ]] && type_valid=true && break
|
|
2815
|
+
done
|
|
2816
|
+
[[ "$type_valid" == "false" ]] && json_err "$E_VALIDATION_FAILED" "Invalid type: $wisdom_type" '{"valid_types":["philosophy","pattern","redirect","stack","decree"]}'
|
|
2817
|
+
|
|
2818
|
+
queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
|
|
2819
|
+
|
|
2820
|
+
# Check if QUEEN.md exists
|
|
2821
|
+
if [[ ! -f "$queen_file" ]]; then
|
|
2822
|
+
json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/docs/QUEEN.md"}'
|
|
2823
|
+
exit 1
|
|
2824
|
+
fi
|
|
2825
|
+
|
|
2826
|
+
# Extract METADATA to get promotion thresholds
|
|
2827
|
+
metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
|
|
2828
|
+
|
|
2829
|
+
# Get threshold for this type (default: philosophy=5, pattern=3, redirect=2, stack=1, decree=0)
|
|
2830
|
+
threshold=$(echo "$metadata" | jq -r ".promotion_thresholds.${wisdom_type} // null")
|
|
2831
|
+
if [[ "$threshold" == "null" ]]; then
|
|
2832
|
+
case "$wisdom_type" in
|
|
2833
|
+
philosophy) threshold=5 ;;
|
|
2834
|
+
pattern) threshold=3 ;;
|
|
2835
|
+
redirect) threshold=2 ;;
|
|
2836
|
+
stack) threshold=1 ;;
|
|
2837
|
+
decree) threshold=0 ;;
|
|
2838
|
+
*) threshold=1 ;;
|
|
2839
|
+
esac
|
|
2840
|
+
fi
|
|
2841
|
+
|
|
2842
|
+
# For decrees, always promote immediately (threshold 0)
|
|
2843
|
+
# For other types, we assume validation count is passed or threshold is met
|
|
2844
|
+
# In a real implementation, this would check a validation counter
|
|
2845
|
+
# For now, we append if threshold allows (decrees always, others need external validation)
|
|
2846
|
+
|
|
2847
|
+
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
2848
|
+
|
|
2849
|
+
# Map type to section header and emoji
|
|
2850
|
+
case "$wisdom_type" in
|
|
2851
|
+
philosophy) section_header="## 📜 Philosophies" ;;
|
|
2852
|
+
pattern) section_header="## 🧭 Patterns" ;;
|
|
2853
|
+
redirect) section_header="## ⚠️ Redirects" ;;
|
|
2854
|
+
stack) section_header="## 🔧 Stack Wisdom" ;;
|
|
2855
|
+
decree) section_header="## 🏛️ Decrees" ;;
|
|
2856
|
+
esac
|
|
2857
|
+
|
|
2858
|
+
# Build the new entry
|
|
2859
|
+
entry="- **${colony_name}** (${ts}): ${content}"
|
|
2860
|
+
|
|
2861
|
+
# Create temp file for atomic write
|
|
2862
|
+
tmp_file="${queen_file}.tmp.$$"
|
|
2863
|
+
|
|
2864
|
+
# Find line numbers for section boundaries
|
|
2865
|
+
section_line=$(grep -n "^${section_header}$" "$queen_file" | head -1 | cut -d: -f1)
|
|
2866
|
+
next_section_line=$(tail -n +$((section_line + 1)) "$queen_file" | grep -n "^## " | head -1 | cut -d: -f1)
|
|
2867
|
+
if [[ -n "$next_section_line" ]]; then
|
|
2868
|
+
section_end=$((section_line + next_section_line - 1))
|
|
2869
|
+
else
|
|
2870
|
+
section_end=$(wc -l < "$queen_file")
|
|
2871
|
+
fi
|
|
2872
|
+
|
|
2873
|
+
# Check if section has placeholder (grep returns 1 when no matches, handle with || true)
|
|
2874
|
+
has_placeholder=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -c "No.*recorded yet" || true)
|
|
2875
|
+
has_placeholder=${has_placeholder:-0}
|
|
2876
|
+
|
|
2877
|
+
if [[ "$has_placeholder" -gt 0 ]]; then
|
|
2878
|
+
# Replace placeholder with entry - only within the target section
|
|
2879
|
+
# Find the specific line number of the placeholder within the section
|
|
2880
|
+
placeholder_line=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -n "^\\*No .* recorded yet" | head -1 | cut -d: -f1)
|
|
2881
|
+
if [[ -n "$placeholder_line" ]]; then
|
|
2882
|
+
actual_line=$((section_line + placeholder_line - 1))
|
|
2883
|
+
sed "${actual_line}c\\
|
|
2884
|
+
${entry}" "$queen_file" > "$tmp_file"
|
|
2885
|
+
else
|
|
2886
|
+
# Fallback: insert after section header
|
|
2887
|
+
sed "${section_line}a\\
|
|
2888
|
+
${entry}" "$queen_file" > "$tmp_file"
|
|
2889
|
+
fi
|
|
2890
|
+
else
|
|
2891
|
+
# Insert entry after the description paragraph (after the second empty line in section)
|
|
2892
|
+
# The structure is: header, blank, description, blank, [entries...]
|
|
2893
|
+
# We want to insert after the blank line following the description
|
|
2894
|
+
empty_lines=$(sed -n "$((section_line + 1)),${section_end}p" "$queen_file" | grep -n "^$" | cut -d: -f1)
|
|
2895
|
+
# Get the second empty line (after description)
|
|
2896
|
+
insert_line=$(echo "$empty_lines" | sed -n '2p')
|
|
2897
|
+
if [[ -n "$insert_line" ]]; then
|
|
2898
|
+
insert_line=$((section_line + insert_line))
|
|
2899
|
+
else
|
|
2900
|
+
# Fallback: use first empty line
|
|
2901
|
+
insert_line=$(echo "$empty_lines" | head -1)
|
|
2902
|
+
if [[ -n "$insert_line" ]]; then
|
|
2903
|
+
insert_line=$((section_line + insert_line))
|
|
2904
|
+
else
|
|
2905
|
+
insert_line=$((section_line + 1))
|
|
2906
|
+
fi
|
|
2907
|
+
fi
|
|
2908
|
+
# Insert the entry after the found line
|
|
2909
|
+
sed "${insert_line}a\\
|
|
2910
|
+
${entry}" "$queen_file" > "$tmp_file"
|
|
2911
|
+
fi
|
|
2912
|
+
|
|
2913
|
+
# Update Evolution Log in temp file
|
|
2914
|
+
ev_entry="| ${ts} | ${colony_name} | promoted_${wisdom_type} | Added: ${content:0:50}... |"
|
|
2915
|
+
# Find the line after the separator in Evolution Log table
|
|
2916
|
+
ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1)
|
|
2917
|
+
|
|
2918
|
+
# Use awk for cross-platform insertion
|
|
2919
|
+
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"
|
|
2920
|
+
|
|
2921
|
+
# Update METADATA stats in temp file
|
|
2922
|
+
# Map wisdom_type to stat key (irregular plurals handled)
|
|
2923
|
+
case "$wisdom_type" in
|
|
2924
|
+
stack) stat_key="total_stack_entries" ;;
|
|
2925
|
+
philosophy) stat_key="total_philosophies" ;;
|
|
2926
|
+
*) stat_key="total_${wisdom_type}s" ;;
|
|
2927
|
+
esac
|
|
2928
|
+
# Read current count from temp file (which has the latest state)
|
|
2929
|
+
current_count=$(grep "\"${stat_key}\":" "$tmp_file" 2>/dev/null | grep -o '[0-9]*' | head -1 || true)
|
|
2930
|
+
current_count=${current_count:-0}
|
|
2931
|
+
new_count=$((current_count + 1))
|
|
2932
|
+
|
|
2933
|
+
# Update last_evolved using awk
|
|
2934
|
+
awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
|
|
2935
|
+
|
|
2936
|
+
# Update stats count using awk
|
|
2937
|
+
awk -v type="$stat_key" -v count="$new_count" '{
|
|
2938
|
+
gsub("\"" type "\": [0-9]*", "\"" type "\": " count)
|
|
2939
|
+
print
|
|
2940
|
+
}' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
|
|
2941
|
+
|
|
2942
|
+
# Add colony to colonies_contributed if not present
|
|
2943
|
+
if ! grep -q "\"${colony_name}\"" "$tmp_file"; then
|
|
2944
|
+
# Add to colonies_contributed array using awk - handle empty and non-empty arrays
|
|
2945
|
+
awk -v colony="$colony_name" '
|
|
2946
|
+
/"colonies_contributed": \[\]/ {
|
|
2947
|
+
gsub(/"colonies_contributed": \[\]/, "\"colonies_contributed\": [\"" colony "\"]")
|
|
2948
|
+
print
|
|
2949
|
+
next
|
|
2950
|
+
}
|
|
2951
|
+
/"colonies_contributed": \[/ && !/\]/ {
|
|
2952
|
+
# Multi-line array, add at next closing bracket
|
|
2953
|
+
print
|
|
2954
|
+
next
|
|
2955
|
+
}
|
|
2956
|
+
/"colonies_contributed": \[/ {
|
|
2957
|
+
# Single-line array with elements
|
|
2958
|
+
gsub(/\]$/, "\"" colony "\", ]")
|
|
2959
|
+
print
|
|
2960
|
+
next
|
|
2961
|
+
}
|
|
2962
|
+
{ print }
|
|
2963
|
+
' "$tmp_file" > "${tmp_file}.col" && mv "${tmp_file}.col" "$tmp_file"
|
|
2964
|
+
fi
|
|
2965
|
+
|
|
2966
|
+
# Atomic move
|
|
2967
|
+
mv "$tmp_file" "$queen_file"
|
|
2968
|
+
|
|
2969
|
+
json_ok "{\"promoted\":true,\"type\":\"$wisdom_type\",\"colony\":\"$colony_name\",\"timestamp\":\"$ts\",\"threshold\":$threshold,\"new_count\":$new_count}"
|
|
2970
|
+
;;
|
|
2971
|
+
|
|
2972
|
+
survey-load)
|
|
2973
|
+
phase_type="${1:-}"
|
|
2974
|
+
survey_dir=".aether/data/survey"
|
|
2975
|
+
|
|
2976
|
+
if [[ ! -d "$survey_dir" ]]; then
|
|
2977
|
+
json_err "$E_FILE_NOT_FOUND" "No survey found"
|
|
2978
|
+
fi
|
|
2979
|
+
|
|
2980
|
+
docs=""
|
|
2981
|
+
case "$phase_type" in
|
|
2982
|
+
*frontend*|*component*|*UI*|*page*|*button*)
|
|
2983
|
+
docs="DISCIPLINES.md,CHAMBERS.md"
|
|
2984
|
+
;;
|
|
2985
|
+
*API*|*endpoint*|*backend*|*route*)
|
|
2986
|
+
docs="BLUEPRINT.md,DISCIPLINES.md"
|
|
2987
|
+
;;
|
|
2988
|
+
*database*|*schema*|*model*|*migration*)
|
|
2989
|
+
docs="BLUEPRINT.md,PROVISIONS.md"
|
|
2990
|
+
;;
|
|
2991
|
+
*test*|*spec*|*coverage*)
|
|
2992
|
+
docs="SENTINEL-PROTOCOLS.md,DISCIPLINES.md"
|
|
2993
|
+
;;
|
|
2994
|
+
*integration*|*external*|*client*)
|
|
2995
|
+
docs="TRAILS.md,PROVISIONS.md"
|
|
2996
|
+
;;
|
|
2997
|
+
*refactor*|*cleanup*|*debt*)
|
|
2998
|
+
docs="PATHOGENS.md,BLUEPRINT.md"
|
|
2999
|
+
;;
|
|
3000
|
+
*setup*|*config*|*initialize*)
|
|
3001
|
+
docs="PROVISIONS.md,CHAMBERS.md"
|
|
3002
|
+
;;
|
|
3003
|
+
*)
|
|
3004
|
+
docs="PROVISIONS.md,BLUEPRINT.md"
|
|
3005
|
+
;;
|
|
3006
|
+
esac
|
|
3007
|
+
|
|
3008
|
+
json_ok "{\"ok\":true,\"docs\":\"$docs\",\"dir\":\"$survey_dir\"}"
|
|
3009
|
+
;;
|
|
3010
|
+
|
|
3011
|
+
survey-verify)
|
|
3012
|
+
survey_dir=".aether/data/survey"
|
|
3013
|
+
required="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
|
|
3014
|
+
missing=""
|
|
3015
|
+
counts=""
|
|
3016
|
+
|
|
3017
|
+
for doc in $required; do
|
|
3018
|
+
if [[ ! -f "$survey_dir/$doc" ]]; then
|
|
3019
|
+
missing="$missing $doc"
|
|
3020
|
+
else
|
|
3021
|
+
lines=$(wc -l < "$survey_dir/$doc" | tr -d ' ')
|
|
3022
|
+
counts="$counts $doc:$lines"
|
|
3023
|
+
fi
|
|
3024
|
+
done
|
|
3025
|
+
|
|
3026
|
+
if [[ -n "$missing" ]]; then
|
|
3027
|
+
json_err "$E_FILE_NOT_FOUND" "Missing survey documents" "{\"missing\":\"$missing\"}"
|
|
3028
|
+
fi
|
|
3029
|
+
|
|
3030
|
+
json_ok "{\"ok\":true,\"counts\":\"$counts\"}"
|
|
3031
|
+
;;
|
|
3032
|
+
|
|
3033
|
+
checkpoint-check)
|
|
3034
|
+
allowlist_file="$DATA_DIR/checkpoint-allowlist.json"
|
|
3035
|
+
|
|
3036
|
+
if [[ ! -f "$allowlist_file" ]]; then
|
|
3037
|
+
json_err "$E_FILE_NOT_FOUND" "Allowlist not found" "{\"path\":\"$allowlist_file\"}"
|
|
3038
|
+
fi
|
|
3039
|
+
|
|
3040
|
+
# Get dirty files from git (staged or unstaged)
|
|
3041
|
+
dirty_files=$(git status --porcelain 2>/dev/null | awk '{print $2}' || true)
|
|
3042
|
+
|
|
3043
|
+
if [[ -z "$dirty_files" ]]; then
|
|
3044
|
+
json_ok '{"ok":true,"system_files":[],"user_files":[],"has_user_files":false}'
|
|
3045
|
+
exit 0
|
|
3046
|
+
fi
|
|
3047
|
+
|
|
3048
|
+
# Temporary files for building JSON
|
|
3049
|
+
system_files_tmp=$(mktemp)
|
|
3050
|
+
user_files_tmp=$(mktemp)
|
|
3051
|
+
|
|
3052
|
+
# Check each file against allowlist patterns
|
|
3053
|
+
for file in $dirty_files; do
|
|
3054
|
+
is_system=false
|
|
3055
|
+
|
|
3056
|
+
# Check against system file patterns
|
|
3057
|
+
if [[ "$file" == ".aether/aether-utils.sh" ]]; then
|
|
3058
|
+
is_system=true
|
|
3059
|
+
elif [[ "$file" == ".aether/workers.md" ]]; then
|
|
3060
|
+
is_system=true
|
|
3061
|
+
elif [[ "$file" == .aether/docs/*.md ]]; then
|
|
3062
|
+
is_system=true
|
|
3063
|
+
elif [[ "$file" == .claude/commands/ant/*.md ]] || [[ "$file" == .claude/commands/ant/**/*.md ]]; then
|
|
3064
|
+
is_system=true
|
|
3065
|
+
elif [[ "$file" == .claude/commands/st/*.md ]] || [[ "$file" == .claude/commands/st/**/*.md ]]; then
|
|
3066
|
+
is_system=true
|
|
3067
|
+
elif [[ "$file" == .opencode/commands/ant/*.md ]] || [[ "$file" == .opencode/commands/ant/**/*.md ]]; then
|
|
3068
|
+
is_system=true
|
|
3069
|
+
elif [[ "$file" == .opencode/agents/*.md ]] || [[ "$file" == .opencode/agents/**/*.md ]]; then
|
|
3070
|
+
is_system=true
|
|
3071
|
+
elif [[ "$file" == runtime/* ]]; then
|
|
3072
|
+
is_system=true
|
|
3073
|
+
elif [[ "$file" == bin/* ]]; then
|
|
3074
|
+
is_system=true
|
|
3075
|
+
fi
|
|
3076
|
+
|
|
3077
|
+
if [[ "$is_system" == "true" ]]; then
|
|
3078
|
+
echo "$file" >> "$system_files_tmp"
|
|
3079
|
+
else
|
|
3080
|
+
echo "$file" >> "$user_files_tmp"
|
|
3081
|
+
fi
|
|
3082
|
+
done
|
|
3083
|
+
|
|
3084
|
+
# Build JSON using jq if available, otherwise use simple format
|
|
3085
|
+
if command -v jq >/dev/null 2>&1; then
|
|
3086
|
+
result=$(jq -n \
|
|
3087
|
+
--argjson system "$(jq -R . < "$system_files_tmp" 2>/dev/null | jq -s .)" \
|
|
3088
|
+
--argjson user "$(jq -R . < "$user_files_tmp" 2>/dev/null | jq -s .)" \
|
|
3089
|
+
'{ok: true, system_files: $system, user_files: $user, has_user_files: ($user | length > 0)}')
|
|
3090
|
+
else
|
|
3091
|
+
# Fallback without jq - simple output
|
|
3092
|
+
system_count=$(wc -l < "$system_files_tmp" 2>/dev/null | tr -d ' ' || echo "0")
|
|
3093
|
+
user_count=$(wc -l < "$user_files_tmp" 2>/dev/null | tr -d ' ' || echo "0")
|
|
3094
|
+
has_user=false
|
|
3095
|
+
[[ "$user_count" -gt 0 ]] && has_user=true
|
|
3096
|
+
result="{\"ok\":true,\"system_files\":[],\"user_files\":[],\"has_user_files\":$has_user}"
|
|
3097
|
+
fi
|
|
3098
|
+
|
|
3099
|
+
rm -f "$system_files_tmp" "$user_files_tmp"
|
|
3100
|
+
echo "$result"
|
|
3101
|
+
exit 0
|
|
3102
|
+
;;
|
|
3103
|
+
|
|
3104
|
+
normalize-args)
|
|
3105
|
+
# Normalize arguments from Claude Code ($ARGUMENTS) or OpenCode ($@)
|
|
3106
|
+
# Usage: bash .aether/aether-utils.sh normalize-args [args...]
|
|
3107
|
+
# Or: eval "$(bash .aether/aether-utils.sh normalize-args)"
|
|
3108
|
+
#
|
|
3109
|
+
# Claude Code passes args in $ARGUMENTS variable
|
|
3110
|
+
# OpenCode passes args in $@ (positional parameters)
|
|
3111
|
+
# This command outputs the normalized arguments as a single string
|
|
3112
|
+
|
|
3113
|
+
normalized=""
|
|
3114
|
+
|
|
3115
|
+
# Try Claude Code style first ($ARGUMENTS environment variable)
|
|
3116
|
+
if [ -n "${ARGUMENTS:-}" ]; then
|
|
3117
|
+
normalized="$ARGUMENTS"
|
|
3118
|
+
# Fall back to OpenCode style ($@ positional params)
|
|
3119
|
+
elif [ $# -gt 0 ]; then
|
|
3120
|
+
# Preserve arguments with spaces by quoting
|
|
3121
|
+
for arg in "$@"; do
|
|
3122
|
+
if [[ "$arg" == *" "* ]] || [[ "$arg" == *"\t"* ]] || [[ "$arg" == *"\n"* ]]; then
|
|
3123
|
+
# Quote arguments containing whitespace
|
|
3124
|
+
normalized="$normalized \"$arg\""
|
|
3125
|
+
else
|
|
3126
|
+
normalized="$normalized $arg"
|
|
3127
|
+
fi
|
|
3128
|
+
done
|
|
3129
|
+
# Trim leading space
|
|
3130
|
+
normalized="${normalized# }"
|
|
3131
|
+
fi
|
|
3132
|
+
|
|
3133
|
+
# Output normalized arguments
|
|
3134
|
+
echo "$normalized"
|
|
3135
|
+
exit 0
|
|
3136
|
+
;;
|
|
3137
|
+
|
|
3138
|
+
# Backward compatibility wrappers for session commands
|
|
3139
|
+
survey-verify-fresh)
|
|
3140
|
+
# Backward compatibility: delegate to session-verify-fresh --command survey
|
|
3141
|
+
# Usage: bash .aether/aether-utils.sh survey-verify-fresh [--force] <survey_start_unixtime>
|
|
3142
|
+
|
|
3143
|
+
force_mode=""
|
|
3144
|
+
survey_start_time=""
|
|
3145
|
+
|
|
3146
|
+
# Parse arguments
|
|
3147
|
+
for arg in "$@"; do
|
|
3148
|
+
if [[ "$arg" == "--force" ]]; then
|
|
3149
|
+
force_mode="--force"
|
|
3150
|
+
elif [[ "$arg" =~ ^[0-9]+$ ]]; then
|
|
3151
|
+
survey_start_time="$arg"
|
|
3152
|
+
fi
|
|
3153
|
+
done
|
|
3154
|
+
|
|
3155
|
+
# Delegate to generic command
|
|
3156
|
+
if [[ -n "$force_mode" ]]; then
|
|
3157
|
+
$0 session-verify-fresh --command survey --force "$survey_start_time"
|
|
3158
|
+
else
|
|
3159
|
+
$0 session-verify-fresh --command survey "$survey_start_time"
|
|
3160
|
+
fi
|
|
3161
|
+
;;
|
|
3162
|
+
|
|
3163
|
+
survey-clear)
|
|
3164
|
+
# Backward compatibility: delegate to session-clear --command survey
|
|
3165
|
+
# Usage: bash .aether/aether-utils.sh survey-clear [--dry-run]
|
|
3166
|
+
|
|
3167
|
+
dry_run=""
|
|
3168
|
+
|
|
3169
|
+
# Parse arguments
|
|
3170
|
+
for arg in "$@"; do
|
|
3171
|
+
if [[ "$arg" == "--dry-run" ]]; then
|
|
3172
|
+
dry_run="--dry-run"
|
|
3173
|
+
fi
|
|
3174
|
+
done
|
|
3175
|
+
|
|
3176
|
+
# Delegate to generic command
|
|
3177
|
+
if [[ "$dry_run" == "--dry-run" ]]; then
|
|
3178
|
+
$0 session-clear --command survey --dry-run
|
|
3179
|
+
else
|
|
3180
|
+
$0 session-clear --command survey
|
|
3181
|
+
fi
|
|
3182
|
+
;;
|
|
3183
|
+
|
|
3184
|
+
session-verify-fresh)
|
|
3185
|
+
# Generic session freshness verification
|
|
3186
|
+
# Usage: bash .aether/aether-utils.sh session-verify-fresh --command <name> [--force] <session_start_unixtime>
|
|
3187
|
+
# Returns: JSON with pass/fail status and file details
|
|
3188
|
+
|
|
3189
|
+
# Parse arguments
|
|
3190
|
+
command_name=""
|
|
3191
|
+
force_mode=""
|
|
3192
|
+
session_start_time=""
|
|
3193
|
+
|
|
3194
|
+
while [[ $# -gt 0 ]]; do
|
|
3195
|
+
case "$1" in
|
|
3196
|
+
--command) command_name="$2"; shift 2 ;;
|
|
3197
|
+
--force) force_mode="--force"; shift ;;
|
|
3198
|
+
*) session_start_time="$1"; shift ;;
|
|
3199
|
+
esac
|
|
3200
|
+
done
|
|
3201
|
+
|
|
3202
|
+
# Validate command name
|
|
3203
|
+
[[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-verify-fresh --command <name> [--force] <session_start>"
|
|
3204
|
+
|
|
3205
|
+
# Map command to directory and files (using env var override pattern)
|
|
3206
|
+
case "$command_name" in
|
|
3207
|
+
survey)
|
|
3208
|
+
session_dir="${SURVEY_DIR:-.aether/data/survey}"
|
|
3209
|
+
required_docs="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
|
|
3210
|
+
;;
|
|
3211
|
+
oracle)
|
|
3212
|
+
session_dir="${ORACLE_DIR:-.aether/oracle}"
|
|
3213
|
+
required_docs="progress.md research.json"
|
|
3214
|
+
;;
|
|
3215
|
+
watch)
|
|
3216
|
+
session_dir="${WATCH_DIR:-.aether/data}"
|
|
3217
|
+
required_docs="watch-status.txt watch-progress.txt"
|
|
3218
|
+
;;
|
|
3219
|
+
swarm)
|
|
3220
|
+
session_dir="${SWARM_DIR:-.aether/data/swarm}"
|
|
3221
|
+
required_docs="findings.json"
|
|
3222
|
+
;;
|
|
3223
|
+
init)
|
|
3224
|
+
session_dir="${INIT_DIR:-.aether/data}"
|
|
3225
|
+
required_docs="COLONY_STATE.json constraints.json"
|
|
3226
|
+
;;
|
|
3227
|
+
seal|entomb)
|
|
3228
|
+
session_dir="${ARCHIVE_DIR:-.aether/data/archive}"
|
|
3229
|
+
required_docs="manifest.json"
|
|
3230
|
+
;;
|
|
3231
|
+
*)
|
|
3232
|
+
json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name" '{"commands":["survey","oracle","watch","swarm","init","seal","entomb"]}'
|
|
3233
|
+
;;
|
|
3234
|
+
esac
|
|
3235
|
+
|
|
3236
|
+
# Initialize result arrays
|
|
3237
|
+
fresh_docs=""
|
|
3238
|
+
stale_docs=""
|
|
3239
|
+
missing_docs=""
|
|
3240
|
+
total_lines=0
|
|
3241
|
+
|
|
3242
|
+
for doc in $required_docs; do
|
|
3243
|
+
doc_path="$session_dir/$doc"
|
|
3244
|
+
|
|
3245
|
+
if [[ ! -f "$doc_path" ]]; then
|
|
3246
|
+
missing_docs="${missing_docs:+$missing_docs }$doc"
|
|
3247
|
+
continue
|
|
3248
|
+
fi
|
|
3249
|
+
|
|
3250
|
+
# Get line count
|
|
3251
|
+
lines=$(wc -l < "$doc_path" 2>/dev/null | tr -d ' ' || echo "0")
|
|
3252
|
+
total_lines=$((total_lines + lines))
|
|
3253
|
+
|
|
3254
|
+
# In force mode, accept any existing file
|
|
3255
|
+
if [[ "$force_mode" == "--force" ]]; then
|
|
3256
|
+
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
3257
|
+
continue
|
|
3258
|
+
fi
|
|
3259
|
+
|
|
3260
|
+
# Check timestamp if session_start_time provided
|
|
3261
|
+
if [[ -n "$session_start_time" ]]; then
|
|
3262
|
+
# Cross-platform stat: macOS uses -f %m, Linux uses -c %Y
|
|
3263
|
+
file_mtime=$(stat -f %m "$doc_path" 2>/dev/null || stat -c %Y "$doc_path" 2>/dev/null || echo "0")
|
|
3264
|
+
|
|
3265
|
+
if [[ "$file_mtime" -ge "$session_start_time" ]]; then
|
|
3266
|
+
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
3267
|
+
else
|
|
3268
|
+
stale_docs="${stale_docs:+$stale_docs }$doc"
|
|
3269
|
+
fi
|
|
3270
|
+
else
|
|
3271
|
+
# No start time provided - accept existing file (backward compatible)
|
|
3272
|
+
fresh_docs="${fresh_docs:+$fresh_docs }$doc"
|
|
3273
|
+
fi
|
|
3274
|
+
done
|
|
3275
|
+
|
|
3276
|
+
# Determine pass/fail
|
|
3277
|
+
# pass = true if: no stale files (fresh files can coexist with missing files)
|
|
3278
|
+
# missing files are ok - they will be created during the session
|
|
3279
|
+
pass=false
|
|
3280
|
+
if [[ "$force_mode" == "--force" ]] || [[ -z "$stale_docs" ]]; then
|
|
3281
|
+
pass=true
|
|
3282
|
+
fi
|
|
3283
|
+
|
|
3284
|
+
# Build JSON response
|
|
3285
|
+
fresh_json=""
|
|
3286
|
+
for item in $fresh_docs; do fresh_json="$fresh_json\"$item\","; done
|
|
3287
|
+
fresh_json="[${fresh_json%,}]"
|
|
3288
|
+
|
|
3289
|
+
stale_json=""
|
|
3290
|
+
for item in $stale_docs; do stale_json="$stale_json\"$item\","; done
|
|
3291
|
+
stale_json="[${stale_json%,}]"
|
|
3292
|
+
|
|
3293
|
+
missing_json=""
|
|
3294
|
+
for item in $missing_docs; do missing_json="$missing_json\"$item\","; done
|
|
3295
|
+
missing_json="[${missing_json%,}]"
|
|
3296
|
+
|
|
3297
|
+
echo "{\"ok\":$pass,\"command\":\"$command_name\",\"fresh\":$fresh_json,\"stale\":$stale_json,\"missing\":$missing_json,\"total_lines\":$total_lines}"
|
|
3298
|
+
exit 0
|
|
3299
|
+
;;
|
|
3300
|
+
|
|
3301
|
+
session-clear)
|
|
3302
|
+
# Generic session file clearing
|
|
3303
|
+
# Usage: bash .aether/aether-utils.sh session-clear --command <name> [--dry-run]
|
|
3304
|
+
|
|
3305
|
+
# Parse arguments
|
|
3306
|
+
command_name=""
|
|
3307
|
+
dry_run=""
|
|
3308
|
+
|
|
3309
|
+
while [[ $# -gt 0 ]]; do
|
|
3310
|
+
case "$1" in
|
|
3311
|
+
--command) command_name="$2"; shift 2 ;;
|
|
3312
|
+
--dry-run) dry_run="--dry-run"; shift ;;
|
|
3313
|
+
*) shift ;;
|
|
3314
|
+
esac
|
|
3315
|
+
done
|
|
3316
|
+
|
|
3317
|
+
[[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-clear --command <name> [--dry-run]"
|
|
3318
|
+
|
|
3319
|
+
# Map command to directory and files
|
|
3320
|
+
case "$command_name" in
|
|
3321
|
+
survey)
|
|
3322
|
+
session_dir="${SURVEY_DIR:-.aether/data/survey}"
|
|
3323
|
+
files="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
|
|
3324
|
+
;;
|
|
3325
|
+
oracle)
|
|
3326
|
+
session_dir="${ORACLE_DIR:-.aether/oracle}"
|
|
3327
|
+
files="progress.md research.json .stop"
|
|
3328
|
+
# Also clear discoveries subdirectory
|
|
3329
|
+
subdir_files="discoveries/*"
|
|
3330
|
+
;;
|
|
3331
|
+
watch)
|
|
3332
|
+
session_dir="${WATCH_DIR:-.aether/data}"
|
|
3333
|
+
files="watch-status.txt watch-progress.txt"
|
|
3334
|
+
;;
|
|
3335
|
+
swarm)
|
|
3336
|
+
session_dir="${SWARM_DIR:-.aether/data/swarm}"
|
|
3337
|
+
files="findings.json display.json timing.json"
|
|
3338
|
+
;;
|
|
3339
|
+
init)
|
|
3340
|
+
# Init clear is destructive - blocked for auto-clear
|
|
3341
|
+
json_err "$E_VALIDATION_FAILED" "Command 'init' is protected and cannot be auto-cleared. Use manual removal of COLONY_STATE.json if absolutely necessary."
|
|
3342
|
+
;;
|
|
3343
|
+
seal|entomb)
|
|
3344
|
+
# Archive operations should never be auto-cleared
|
|
3345
|
+
json_err "$E_VALIDATION_FAILED" "Command '$command_name' is protected and cannot be auto-cleared. Archives and chambers must be managed manually."
|
|
3346
|
+
;;
|
|
3347
|
+
*)
|
|
3348
|
+
json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name"
|
|
3349
|
+
;;
|
|
3350
|
+
esac
|
|
3351
|
+
|
|
3352
|
+
cleared=""
|
|
3353
|
+
errors=""
|
|
3354
|
+
|
|
3355
|
+
if [[ -d "$session_dir" && -n "$files" ]]; then
|
|
3356
|
+
for doc in $files; do
|
|
3357
|
+
doc_path="$session_dir/$doc"
|
|
3358
|
+
if [[ -f "$doc_path" ]]; then
|
|
3359
|
+
if [[ "$dry_run" == "--dry-run" ]]; then
|
|
3360
|
+
cleared="$cleared $doc"
|
|
3361
|
+
else
|
|
3362
|
+
if rm -f "$doc_path" 2>/dev/null; then
|
|
3363
|
+
cleared="$cleared $doc"
|
|
3364
|
+
else
|
|
3365
|
+
errors="$errors $doc"
|
|
3366
|
+
fi
|
|
3367
|
+
fi
|
|
3368
|
+
fi
|
|
3369
|
+
done
|
|
3370
|
+
|
|
3371
|
+
# Handle oracle discoveries subdirectory
|
|
3372
|
+
if [[ "$command_name" == "oracle" && -d "$session_dir/discoveries" ]]; then
|
|
3373
|
+
if [[ "$dry_run" == "--dry-run" ]]; then
|
|
3374
|
+
cleared="$cleared discoveries/"
|
|
3375
|
+
else
|
|
3376
|
+
rm -rf "$session_dir/discoveries" 2>/dev/null && cleared="$cleared discoveries/" || errors="$errors discoveries/"
|
|
3377
|
+
fi
|
|
3378
|
+
fi
|
|
3379
|
+
fi
|
|
3380
|
+
|
|
3381
|
+
json_ok "{\"command\":\"$command_name\",\"cleared\":\"${cleared// /}\",\"errors\":\"${errors// /}\",\"dry_run\":$([[ "$dry_run" == "--dry-run" ]] && echo "true" || echo "false")}"
|
|
3382
|
+
;;
|
|
3383
|
+
|
|
3384
|
+
pheromone-export)
|
|
3385
|
+
# Export pheromones to eternal XML format
|
|
3386
|
+
# Usage: pheromone-export [input_json] [output_xml]
|
|
3387
|
+
# input_json: Path to pheromones.json (default: .aether/data/pheromones.json)
|
|
3388
|
+
# output_xml: Path to output XML (default: ~/.aether/eternal/pheromones.xml)
|
|
3389
|
+
|
|
3390
|
+
input_json="${1:-.aether/data/pheromones.json}"
|
|
3391
|
+
output_xml="${2:-$HOME/.aether/eternal/pheromones.xml}"
|
|
3392
|
+
schema_file="${3:-$SCRIPT_DIR/schemas/pheromone.xsd}"
|
|
3393
|
+
|
|
3394
|
+
# Ensure xml-utils.sh is sourced
|
|
3395
|
+
if ! type pheromone-export &>/dev/null; then
|
|
3396
|
+
[[ -f "$SCRIPT_DIR/utils/xml-utils.sh" ]] && source "$SCRIPT_DIR/utils/xml-utils.sh"
|
|
3397
|
+
fi
|
|
3398
|
+
|
|
3399
|
+
if type pheromone-export &>/dev/null; then
|
|
3400
|
+
pheromone-export "$input_json" "$output_xml" "$schema_file"
|
|
3401
|
+
else
|
|
3402
|
+
json_err "$E_DEPENDENCY_MISSING" "xml-utils.sh not available for pheromone export"
|
|
3403
|
+
fi
|
|
3404
|
+
;;
|
|
3405
|
+
|
|
3406
|
+
# ============================================================================
|
|
3407
|
+
# Session Continuity Commands
|
|
3408
|
+
# ============================================================================
|
|
3409
|
+
|
|
3410
|
+
session-init)
|
|
3411
|
+
# Initialize a new session tracking file
|
|
3412
|
+
# Usage: session-init [session_id] [goal]
|
|
3413
|
+
session_id="${2:-$(date +%s)_$(openssl rand -hex 4 2>/dev/null || echo $$)}"
|
|
3414
|
+
goal="${3:-}"
|
|
3415
|
+
|
|
3416
|
+
session_file="$DATA_DIR/session.json"
|
|
3417
|
+
|
|
3418
|
+
cat > "$session_file" << EOF
|
|
3419
|
+
{
|
|
3420
|
+
"session_id": "$session_id",
|
|
3421
|
+
"started_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
|
3422
|
+
"last_command": null,
|
|
3423
|
+
"last_command_at": null,
|
|
3424
|
+
"colony_goal": "$goal",
|
|
3425
|
+
"current_phase": 0,
|
|
3426
|
+
"current_milestone": "First Mound",
|
|
3427
|
+
"suggested_next": "/ant:plan",
|
|
3428
|
+
"context_cleared": false,
|
|
3429
|
+
"resumed_at": null,
|
|
3430
|
+
"active_todos": [],
|
|
3431
|
+
"summary": "Session initialized"
|
|
3432
|
+
}
|
|
3433
|
+
EOF
|
|
3434
|
+
json_ok "{\"session_id\":\"$session_id\",\"goal\":\"$goal\",\"file\":\"$session_file\"}"
|
|
3435
|
+
;;
|
|
3436
|
+
|
|
3437
|
+
session-update)
|
|
3438
|
+
# Update session with latest activity
|
|
3439
|
+
# Usage: session-update <command> [suggested_next] [summary]
|
|
3440
|
+
cmd_run="${2:-}"
|
|
3441
|
+
suggested="${3:-}"
|
|
3442
|
+
summary="${4:-}"
|
|
3443
|
+
|
|
3444
|
+
session_file="$DATA_DIR/session.json"
|
|
3445
|
+
|
|
3446
|
+
if [[ ! -f "$session_file" ]]; then
|
|
3447
|
+
# Auto-initialize if doesn't exist
|
|
3448
|
+
bash "$0" session-init "auto_$(date +%s)" ""
|
|
3449
|
+
fi
|
|
3450
|
+
|
|
3451
|
+
# Read current session
|
|
3452
|
+
current_session=$(cat "$session_file" 2>/dev/null || echo '{}')
|
|
3453
|
+
|
|
3454
|
+
# Extract current values for preservation
|
|
3455
|
+
current_goal=$(echo "$current_session" | jq -r '.colony_goal // empty')
|
|
3456
|
+
current_phase=$(echo "$current_session" | jq -r '.current_phase // 0')
|
|
3457
|
+
current_milestone=$(echo "$current_session" | jq -r '.current_milestone // "First Mound"')
|
|
3458
|
+
|
|
3459
|
+
# Get top 3 TODOs if TO-DOs.md exists
|
|
3460
|
+
todos="[]"
|
|
3461
|
+
if [[ -f "TO-DOs.md" ]]; then
|
|
3462
|
+
todos=$(grep "^### " TO-DOs.md 2>/dev/null | head -3 | sed 's/^### //' | jq -R . | jq -s .)
|
|
3463
|
+
fi
|
|
3464
|
+
|
|
3465
|
+
# Get colony state if exists
|
|
3466
|
+
if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
|
|
3467
|
+
current_goal=$(jq -r '.goal // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_goal")
|
|
3468
|
+
current_phase=$(jq -r '.current_phase // 0' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_phase")
|
|
3469
|
+
current_milestone=$(jq -r '.milestone // "First Mound"' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_milestone")
|
|
3470
|
+
fi
|
|
3471
|
+
|
|
3472
|
+
# Build updated session
|
|
3473
|
+
echo "$current_session" | jq --arg cmd "$cmd_run" \
|
|
3474
|
+
--arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
3475
|
+
--arg suggested "$suggested" \
|
|
3476
|
+
--arg summary "$summary" \
|
|
3477
|
+
--arg goal "$current_goal" \
|
|
3478
|
+
--argjson phase "$current_phase" \
|
|
3479
|
+
--arg milestone "$current_milestone" \
|
|
3480
|
+
--argjson todos "$todos" \
|
|
3481
|
+
'.last_command = $cmd |
|
|
3482
|
+
.last_command_at = $ts |
|
|
3483
|
+
.suggested_next = $suggested |
|
|
3484
|
+
.summary = $summary |
|
|
3485
|
+
.colony_goal = $goal |
|
|
3486
|
+
.current_phase = $phase |
|
|
3487
|
+
.current_milestone = $milestone |
|
|
3488
|
+
.active_todos = $todos' > "$session_file"
|
|
3489
|
+
|
|
3490
|
+
json_ok "{\"updated\":true,\"command\":\"$cmd_run\"}"
|
|
3491
|
+
;;
|
|
3492
|
+
|
|
3493
|
+
session-read)
|
|
3494
|
+
# Read and return current session state
|
|
3495
|
+
session_file="$DATA_DIR/session.json"
|
|
3496
|
+
|
|
3497
|
+
if [[ ! -f "$session_file" ]]; then
|
|
3498
|
+
json_ok "{\"exists\":false,\"session\":null}"
|
|
3499
|
+
exit 0
|
|
3500
|
+
fi
|
|
3501
|
+
|
|
3502
|
+
session_data=$(cat "$session_file" 2>/dev/null || echo '{}')
|
|
3503
|
+
|
|
3504
|
+
# Check if stale (> 24 hours)
|
|
3505
|
+
last_cmd_ts="" is_stale="" age_hours=""
|
|
3506
|
+
last_cmd_ts=$(echo "$session_data" | jq -r '.last_command_at // .started_at // empty')
|
|
3507
|
+
if [[ -n "$last_cmd_ts" ]]; then
|
|
3508
|
+
last_epoch=0 now_epoch=0
|
|
3509
|
+
last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null || echo 0)
|
|
3510
|
+
now_epoch=$(date +%s)
|
|
3511
|
+
age_hours=$(( (now_epoch - last_epoch) / 3600 ))
|
|
3512
|
+
[[ $age_hours -gt 24 ]] && is_stale=true || is_stale=false
|
|
3513
|
+
else
|
|
3514
|
+
is_stale="false"
|
|
3515
|
+
age_hours="unknown"
|
|
3516
|
+
fi
|
|
3517
|
+
|
|
3518
|
+
json_ok "{\"exists\":true,\"is_stale\":$is_stale,\"age_hours\":$age_hours,\"session\":$session_data}"
|
|
3519
|
+
;;
|
|
3520
|
+
|
|
3521
|
+
session-is-stale)
|
|
3522
|
+
# Check if session is stale (returns true/false)
|
|
3523
|
+
session_file="$DATA_DIR/session.json"
|
|
3524
|
+
|
|
3525
|
+
if [[ ! -f "$session_file" ]]; then
|
|
3526
|
+
echo "true"
|
|
3527
|
+
exit 0
|
|
3528
|
+
fi
|
|
3529
|
+
|
|
3530
|
+
last_cmd_ts=$(jq -r '.last_command_at // .started_at // empty' "$session_file" 2>/dev/null)
|
|
3531
|
+
|
|
3532
|
+
if [[ -z "$last_cmd_ts" ]]; then
|
|
3533
|
+
echo "true"
|
|
3534
|
+
exit 0
|
|
3535
|
+
fi
|
|
3536
|
+
|
|
3537
|
+
last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null || echo 0)
|
|
3538
|
+
now_epoch=$(date +%s)
|
|
3539
|
+
age_hours=$(( (now_epoch - last_epoch) / 3600 ))
|
|
3540
|
+
|
|
3541
|
+
[[ $age_hours -gt 24 ]] && echo "true" || echo "false"
|
|
3542
|
+
;;
|
|
3543
|
+
|
|
3544
|
+
session-clear)
|
|
3545
|
+
# Mark session as cleared (preserves file but marks context_cleared)
|
|
3546
|
+
preserve="${2:-false}"
|
|
3547
|
+
session_file="$DATA_DIR/session.json"
|
|
3548
|
+
|
|
3549
|
+
if [[ -f "$session_file" ]]; then
|
|
3550
|
+
if [[ "$preserve" == "true" ]]; then
|
|
3551
|
+
# Just mark as cleared
|
|
3552
|
+
jq '.context_cleared = true' "$session_file" > "$session_file.tmp" && mv "$session_file.tmp" "$session_file"
|
|
3553
|
+
json_ok "{\"cleared\":true,\"preserved\":true}"
|
|
3554
|
+
else
|
|
3555
|
+
# Remove file entirely
|
|
3556
|
+
rm -f "$session_file"
|
|
3557
|
+
json_ok "{\"cleared\":true,\"preserved\":false}"
|
|
3558
|
+
fi
|
|
3559
|
+
else
|
|
3560
|
+
json_ok "{\"cleared\":false,\"reason\":\"no_session_exists\"}"
|
|
3561
|
+
fi
|
|
3562
|
+
;;
|
|
3563
|
+
|
|
3564
|
+
session-mark-resumed)
|
|
3565
|
+
# Mark session as resumed
|
|
3566
|
+
session_file="$DATA_DIR/session.json"
|
|
3567
|
+
|
|
3568
|
+
if [[ -f "$session_file" ]]; then
|
|
3569
|
+
jq --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
3570
|
+
'.resumed_at = $ts | .context_cleared = false' "$session_file" > "$session_file.tmp" && mv "$session_file.tmp" "$session_file"
|
|
3571
|
+
json_ok "{\"resumed\":true,\"timestamp\":\"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"}"
|
|
3572
|
+
else
|
|
3573
|
+
json_err "$E_RESOURCE_NOT_FOUND" "No session to mark as resumed"
|
|
3574
|
+
fi
|
|
3575
|
+
;;
|
|
3576
|
+
|
|
3577
|
+
session-summary)
|
|
3578
|
+
# Get human-readable session summary
|
|
3579
|
+
session_file="$DATA_DIR/session.json"
|
|
3580
|
+
|
|
3581
|
+
if [[ ! -f "$session_file" ]]; then
|
|
3582
|
+
echo "No active session found."
|
|
3583
|
+
exit 0
|
|
3584
|
+
fi
|
|
3585
|
+
|
|
3586
|
+
goal=$(jq -r '.colony_goal // "No goal set"' "$session_file")
|
|
3587
|
+
phase=$(jq -r '.current_phase // 0' "$session_file")
|
|
3588
|
+
milestone=$(jq -r '.current_milestone // "First Mound"' "$session_file")
|
|
3589
|
+
last_cmd=$(jq -r '.last_command // "None"' "$session_file")
|
|
3590
|
+
last_at=$(jq -r '.last_command_at // "Unknown"' "$session_file")
|
|
3591
|
+
suggested=$(jq -r '.suggested_next // "None"' "$session_file")
|
|
3592
|
+
cleared=$(jq -r '.context_cleared // false' "$session_file")
|
|
3593
|
+
|
|
3594
|
+
echo "📋 Session Summary"
|
|
3595
|
+
echo "=================="
|
|
3596
|
+
echo "Goal: $goal"
|
|
3597
|
+
[[ "$phase" != "0" ]] && echo "Phase: $phase"
|
|
3598
|
+
echo "Milestone: $milestone"
|
|
3599
|
+
echo "Last Command: $last_cmd"
|
|
3600
|
+
echo "Last Active: $last_at"
|
|
3601
|
+
[[ "$suggested" != "None" ]] && echo "Suggested Next: $suggested"
|
|
3602
|
+
[[ "$cleared" == "true" ]] && echo "Status: Context was cleared"
|
|
3603
|
+
;;
|
|
3604
|
+
|
|
2239
3605
|
*)
|
|
2240
|
-
json_err "Unknown command: $cmd"
|
|
3606
|
+
json_err "$E_VALIDATION_FAILED" "Unknown command: $cmd"
|
|
2241
3607
|
;;
|
|
2242
3608
|
esac
|