aether-colony 5.1.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.aether/aether-utils.sh +157 -42
  2. package/.aether/agents/aether-ambassador.md +140 -0
  3. package/.aether/agents/aether-archaeologist.md +108 -0
  4. package/.aether/agents/aether-architect.md +133 -0
  5. package/.aether/agents/aether-auditor.md +144 -0
  6. package/.aether/agents/aether-builder.md +184 -0
  7. package/.aether/agents/aether-chaos.md +115 -0
  8. package/.aether/agents/aether-chronicler.md +122 -0
  9. package/.aether/agents/aether-gatekeeper.md +116 -0
  10. package/.aether/agents/aether-includer.md +117 -0
  11. package/.aether/agents/aether-keeper.md +177 -0
  12. package/.aether/agents/aether-measurer.md +128 -0
  13. package/.aether/agents/aether-oracle.md +137 -0
  14. package/.aether/agents/aether-probe.md +133 -0
  15. package/.aether/agents/aether-queen.md +286 -0
  16. package/.aether/agents/aether-route-setter.md +130 -0
  17. package/.aether/agents/aether-sage.md +106 -0
  18. package/.aether/agents/aether-scout.md +101 -0
  19. package/.aether/agents/aether-surveyor-disciplines.md +391 -0
  20. package/.aether/agents/aether-surveyor-nest.md +329 -0
  21. package/.aether/agents/aether-surveyor-pathogens.md +264 -0
  22. package/.aether/agents/aether-surveyor-provisions.md +334 -0
  23. package/.aether/agents/aether-tracker.md +137 -0
  24. package/.aether/agents/aether-watcher.md +174 -0
  25. package/.aether/agents/aether-weaver.md +130 -0
  26. package/.aether/commands/claude/archaeology.md +334 -0
  27. package/.aether/commands/claude/build.md +65 -0
  28. package/.aether/commands/claude/chaos.md +336 -0
  29. package/.aether/commands/claude/colonize.md +259 -0
  30. package/.aether/commands/claude/continue.md +60 -0
  31. package/.aether/commands/claude/council.md +507 -0
  32. package/.aether/commands/claude/data-clean.md +81 -0
  33. package/.aether/commands/claude/dream.md +268 -0
  34. package/.aether/commands/claude/entomb.md +498 -0
  35. package/.aether/commands/claude/export-signals.md +57 -0
  36. package/.aether/commands/claude/feedback.md +96 -0
  37. package/.aether/commands/claude/flag.md +151 -0
  38. package/.aether/commands/claude/flags.md +169 -0
  39. package/.aether/commands/claude/focus.md +76 -0
  40. package/.aether/commands/claude/help.md +154 -0
  41. package/.aether/commands/claude/history.md +140 -0
  42. package/.aether/commands/claude/import-signals.md +71 -0
  43. package/.aether/commands/claude/init.md +505 -0
  44. package/.aether/commands/claude/insert-phase.md +105 -0
  45. package/.aether/commands/claude/interpret.md +278 -0
  46. package/.aether/commands/claude/lay-eggs.md +210 -0
  47. package/.aether/commands/claude/maturity.md +113 -0
  48. package/.aether/commands/claude/memory-details.md +77 -0
  49. package/.aether/commands/claude/migrate-state.md +171 -0
  50. package/.aether/commands/claude/oracle.md +642 -0
  51. package/.aether/commands/claude/organize.md +232 -0
  52. package/.aether/commands/claude/patrol.md +620 -0
  53. package/.aether/commands/claude/pause-colony.md +233 -0
  54. package/.aether/commands/claude/phase.md +115 -0
  55. package/.aether/commands/claude/pheromones.md +156 -0
  56. package/.aether/commands/claude/plan.md +693 -0
  57. package/.aether/commands/claude/preferences.md +65 -0
  58. package/.aether/commands/claude/quick.md +100 -0
  59. package/.aether/commands/claude/redirect.md +76 -0
  60. package/.aether/commands/claude/resume-colony.md +197 -0
  61. package/.aether/commands/claude/resume.md +388 -0
  62. package/.aether/commands/claude/run.md +231 -0
  63. package/.aether/commands/claude/seal.md +774 -0
  64. package/.aether/commands/claude/skill-create.md +286 -0
  65. package/.aether/commands/claude/status.md +410 -0
  66. package/.aether/commands/claude/swarm.md +349 -0
  67. package/.aether/commands/claude/tunnels.md +426 -0
  68. package/.aether/commands/claude/update.md +132 -0
  69. package/.aether/commands/claude/verify-castes.md +143 -0
  70. package/.aether/commands/claude/watch.md +239 -0
  71. package/.aether/commands/colonize.yaml +4 -0
  72. package/.aether/commands/council.yaml +205 -0
  73. package/.aether/commands/init.yaml +46 -13
  74. package/.aether/commands/insert-phase.yaml +4 -0
  75. package/.aether/commands/opencode/archaeology.md +331 -0
  76. package/.aether/commands/opencode/build.md +1168 -0
  77. package/.aether/commands/opencode/chaos.md +329 -0
  78. package/.aether/commands/opencode/colonize.md +195 -0
  79. package/.aether/commands/opencode/continue.md +1436 -0
  80. package/.aether/commands/opencode/council.md +437 -0
  81. package/.aether/commands/opencode/data-clean.md +77 -0
  82. package/.aether/commands/opencode/dream.md +260 -0
  83. package/.aether/commands/opencode/entomb.md +377 -0
  84. package/.aether/commands/opencode/export-signals.md +54 -0
  85. package/.aether/commands/opencode/feedback.md +99 -0
  86. package/.aether/commands/opencode/flag.md +149 -0
  87. package/.aether/commands/opencode/flags.md +167 -0
  88. package/.aether/commands/opencode/focus.md +73 -0
  89. package/.aether/commands/opencode/help.md +157 -0
  90. package/.aether/commands/opencode/history.md +136 -0
  91. package/.aether/commands/opencode/import-signals.md +68 -0
  92. package/.aether/commands/opencode/init.md +518 -0
  93. package/.aether/commands/opencode/insert-phase.md +111 -0
  94. package/.aether/commands/opencode/interpret.md +272 -0
  95. package/.aether/commands/opencode/lay-eggs.md +213 -0
  96. package/.aether/commands/opencode/maturity.md +108 -0
  97. package/.aether/commands/opencode/memory-details.md +83 -0
  98. package/.aether/commands/opencode/migrate-state.md +165 -0
  99. package/.aether/commands/opencode/oracle.md +593 -0
  100. package/.aether/commands/opencode/organize.md +226 -0
  101. package/.aether/commands/opencode/patrol.md +626 -0
  102. package/.aether/commands/opencode/pause-colony.md +203 -0
  103. package/.aether/commands/opencode/phase.md +113 -0
  104. package/.aether/commands/opencode/pheromones.md +162 -0
  105. package/.aether/commands/opencode/plan.md +684 -0
  106. package/.aether/commands/opencode/preferences.md +71 -0
  107. package/.aether/commands/opencode/quick.md +91 -0
  108. package/.aether/commands/opencode/redirect.md +84 -0
  109. package/.aether/commands/opencode/resume-colony.md +190 -0
  110. package/.aether/commands/opencode/resume.md +394 -0
  111. package/.aether/commands/opencode/run.md +237 -0
  112. package/.aether/commands/opencode/seal.md +452 -0
  113. package/.aether/commands/opencode/skill-create.md +63 -0
  114. package/.aether/commands/opencode/status.md +307 -0
  115. package/.aether/commands/opencode/swarm.md +15 -0
  116. package/.aether/commands/opencode/tunnels.md +400 -0
  117. package/.aether/commands/opencode/update.md +127 -0
  118. package/.aether/commands/opencode/verify-castes.md +139 -0
  119. package/.aether/commands/opencode/watch.md +227 -0
  120. package/.aether/commands/plan.yaml +53 -2
  121. package/.aether/commands/quick.yaml +104 -0
  122. package/.aether/commands/resume-colony.yaml +6 -4
  123. package/.aether/commands/resume.yaml +9 -0
  124. package/.aether/commands/run.yaml +37 -1
  125. package/.aether/commands/seal.yaml +9 -0
  126. package/.aether/commands/status.yaml +45 -1
  127. package/.aether/docs/command-playbooks/build-full.md +3 -2
  128. package/.aether/docs/command-playbooks/build-prep.md +12 -4
  129. package/.aether/docs/command-playbooks/build-verify.md +51 -0
  130. package/.aether/docs/command-playbooks/continue-advance.md +115 -6
  131. package/.aether/docs/command-playbooks/continue-full.md +1 -0
  132. package/.aether/docs/command-playbooks/continue-verify.md +33 -0
  133. package/.aether/utils/clash-detect.sh +239 -0
  134. package/.aether/utils/council.sh +425 -0
  135. package/.aether/utils/error-handler.sh +3 -3
  136. package/.aether/utils/flag.sh +23 -12
  137. package/.aether/utils/hive.sh +2 -2
  138. package/.aether/utils/hooks/clash-pre-tool-use.js +99 -0
  139. package/.aether/utils/immune.sh +508 -0
  140. package/.aether/utils/learning.sh +2 -2
  141. package/.aether/utils/merge-driver-lockfile.sh +35 -0
  142. package/.aether/utils/midden.sh +712 -0
  143. package/.aether/utils/pheromone.sh +1376 -108
  144. package/.aether/utils/queen.sh +31 -21
  145. package/.aether/utils/session.sh +264 -0
  146. package/.aether/utils/spawn-tree.sh +7 -7
  147. package/.aether/utils/spawn.sh +2 -2
  148. package/.aether/utils/state-api.sh +216 -5
  149. package/.aether/utils/swarm.sh +1 -1
  150. package/.aether/utils/worktree.sh +189 -0
  151. package/.claude/commands/ant/colonize.md +2 -0
  152. package/.claude/commands/ant/council.md +205 -0
  153. package/.claude/commands/ant/init.md +53 -14
  154. package/.claude/commands/ant/insert-phase.md +4 -0
  155. package/.claude/commands/ant/plan.md +27 -1
  156. package/.claude/commands/ant/quick.md +100 -0
  157. package/.claude/commands/ant/resume-colony.md +3 -2
  158. package/.claude/commands/ant/resume.md +9 -0
  159. package/.claude/commands/ant/run.md +37 -1
  160. package/.claude/commands/ant/seal.md +9 -0
  161. package/.claude/commands/ant/status.md +45 -1
  162. package/.opencode/commands/ant/colonize.md +2 -0
  163. package/.opencode/commands/ant/council.md +143 -0
  164. package/.opencode/commands/ant/init.md +53 -13
  165. package/.opencode/commands/ant/insert-phase.md +4 -0
  166. package/.opencode/commands/ant/plan.md +26 -1
  167. package/.opencode/commands/ant/quick.md +91 -0
  168. package/.opencode/commands/ant/resume-colony.md +3 -2
  169. package/.opencode/commands/ant/resume.md +9 -0
  170. package/.opencode/commands/ant/run.md +37 -1
  171. package/.opencode/commands/ant/status.md +2 -0
  172. package/CHANGELOG.md +116 -0
  173. package/README.md +34 -8
  174. package/bin/cli.js +103 -61
  175. package/bin/lib/banner.js +14 -0
  176. package/bin/lib/init.js +8 -7
  177. package/bin/lib/interactive-setup.js +251 -0
  178. package/bin/npx-entry.js +21 -0
  179. package/bin/npx-install.js +9 -167
  180. package/bin/validate-package.sh +23 -0
  181. package/package.json +11 -3
  182. package/.aether/docs/plans/pheromone-display-plan.md +0 -257
  183. package/.aether/schemas/example-prompt-builder.xml +0 -234
  184. package/.aether/scripts/incident-test-add.sh +0 -47
  185. package/.aether/scripts/weekly-audit.sh +0 -79
@@ -0,0 +1,508 @@
1
+ #!/bin/bash
2
+ # Immune response system — trophallaxis repair and scarification
3
+ # Provides: _trophallaxis_diagnose, _trophallaxis_retry, _scar_add, _scar_list,
4
+ # _scar_check, _immune_auto_scar
5
+ #
6
+ # These functions are sourced by aether-utils.sh at startup.
7
+ # All shared infrastructure (json_ok, json_err, atomic_write, acquire_lock,
8
+ # release_lock, LOCK_DIR, COLONY_DATA_DIR, error constants) is available.
9
+
10
+ # ---------------------------------------------------------------------------
11
+ # Internal helpers
12
+ # ---------------------------------------------------------------------------
13
+
14
+ _immune_data_dir() {
15
+ echo "$COLONY_DATA_DIR/immune"
16
+ }
17
+
18
+ _immune_scars_file() {
19
+ echo "$COLONY_DATA_DIR/immune/scars.json"
20
+ }
21
+
22
+ _immune_retry_log_file() {
23
+ echo "$COLONY_DATA_DIR/immune/retry-log.json"
24
+ }
25
+
26
+ _immune_ensure_dir() {
27
+ mkdir -p "$(_immune_data_dir)"
28
+ }
29
+
30
+ _immune_ensure_scars_file() {
31
+ local sf
32
+ sf="$(_immune_scars_file)"
33
+ _immune_ensure_dir
34
+ if [[ ! -f "$sf" ]]; then
35
+ printf '%s\n' '{"version":"1.0","scars":[]}' > "$sf"
36
+ fi
37
+ }
38
+
39
+ _immune_ensure_retry_log() {
40
+ local rf
41
+ rf="$(_immune_retry_log_file)"
42
+ _immune_ensure_dir
43
+ if [[ ! -f "$rf" ]]; then
44
+ printf '%s\n' '{"version":"1.0","tasks":{}}' > "$rf"
45
+ fi
46
+ }
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # _trophallaxis_diagnose
50
+ # Usage: trophallaxis-diagnose --task-id <id> --failure <desc> [--phase N]
51
+ # ---------------------------------------------------------------------------
52
+ _trophallaxis_diagnose() {
53
+ local td_task_id=""
54
+ local td_failure=""
55
+ local td_phase=""
56
+
57
+ while [[ $# -gt 0 ]]; do
58
+ case "$1" in
59
+ --task-id) td_task_id="${2:-}"; shift 2 ;;
60
+ --failure) td_failure="${2:-}"; shift 2 ;;
61
+ --phase) td_phase="${2:-}"; shift 2 ;;
62
+ *) shift ;;
63
+ esac
64
+ done
65
+
66
+ if [[ -z "$td_task_id" ]]; then
67
+ json_err "$E_VALIDATION_FAILED" "trophallaxis-diagnose requires --task-id"
68
+ return
69
+ fi
70
+
71
+ if [[ -z "$td_failure" ]]; then
72
+ json_err "$E_VALIDATION_FAILED" "trophallaxis-diagnose requires --failure"
73
+ return
74
+ fi
75
+
76
+ local td_midden_file="$COLONY_DATA_DIR/midden/midden.json"
77
+ local td_related=0
78
+ local td_related_entries="[]"
79
+ local td_approach=""
80
+
81
+ # Search midden for related entries using keywords from failure description
82
+ if [[ -f "$td_midden_file" ]]; then
83
+ # Extract first keyword (longest word >= 4 chars) for search
84
+ local td_keyword
85
+ td_keyword=$(echo "$td_failure" | tr '[:upper:]' '[:lower:]' | \
86
+ grep -oE '[a-z]{4,}' | sort -rn -k1,1 | head -1 || echo "")
87
+
88
+ if [[ -n "$td_keyword" ]]; then
89
+ td_related=$(jq \
90
+ --arg q "$td_keyword" \
91
+ '[.entries // [] | .[] |
92
+ select(.acknowledged != true) |
93
+ select(.message | ascii_downcase | contains($q))
94
+ ] | length' "$td_midden_file" 2>/dev/null || echo "0")
95
+
96
+ td_related_entries=$(jq \
97
+ --arg q "$td_keyword" \
98
+ '[.entries // [] | .[] |
99
+ select(.acknowledged != true) |
100
+ select(.message | ascii_downcase | contains($q)) |
101
+ {id, timestamp, category, source, message}
102
+ ] | .[:5]' "$td_midden_file" 2>/dev/null || echo "[]")
103
+ else
104
+ # No usable keyword — scan all recent entries
105
+ td_related=$(jq '[.entries // [] | .[] | select(.acknowledged != true)] | length' \
106
+ "$td_midden_file" 2>/dev/null || echo "0")
107
+ fi
108
+ fi
109
+
110
+ # Build diagnosis text
111
+ local td_diagnosis
112
+ if [[ "$td_related" -gt 0 ]]; then
113
+ td_diagnosis="Found $td_related related failure(s) in midden matching: $td_failure"
114
+ td_approach="Review related midden entries for patterns. Address root cause before retrying."
115
+ else
116
+ td_diagnosis="No related failures found in midden for: $td_failure"
117
+ td_approach="Investigate failure from first principles. Check logs and dependencies."
118
+ fi
119
+
120
+ # Confidence: higher when related failures exist
121
+ local td_confidence
122
+ if [[ "$td_related" -ge 3 ]]; then
123
+ td_confidence="0.9"
124
+ elif [[ "$td_related" -ge 1 ]]; then
125
+ td_confidence="0.7"
126
+ else
127
+ td_confidence="0.4"
128
+ fi
129
+
130
+ json_ok "$(jq -n \
131
+ --arg task_id "$td_task_id" \
132
+ --arg failure "$td_failure" \
133
+ --arg diagnosis "$td_diagnosis" \
134
+ --argjson related_failures "$td_related" \
135
+ --arg suggested_approach "$td_approach" \
136
+ --argjson confidence "$td_confidence" \
137
+ --argjson related_entries "$td_related_entries" \
138
+ '{
139
+ task_id: $task_id,
140
+ failure: $failure,
141
+ diagnosis: $diagnosis,
142
+ related_failures: $related_failures,
143
+ suggested_approach: $suggested_approach,
144
+ confidence: $confidence,
145
+ related_entries: $related_entries
146
+ }')"
147
+ }
148
+
149
+ # ---------------------------------------------------------------------------
150
+ # _trophallaxis_retry
151
+ # Usage: trophallaxis-retry --task-id <id> --diagnosis <json>
152
+ # ---------------------------------------------------------------------------
153
+ _trophallaxis_retry() {
154
+ local tr_task_id=""
155
+ local tr_diagnosis=""
156
+
157
+ while [[ $# -gt 0 ]]; do
158
+ case "$1" in
159
+ --task-id) tr_task_id="${2:-}"; shift 2 ;;
160
+ --diagnosis) tr_diagnosis="${2:-}"; shift 2 ;;
161
+ *) shift ;;
162
+ esac
163
+ done
164
+
165
+ if [[ -z "$tr_task_id" ]]; then
166
+ json_err "$E_VALIDATION_FAILED" "trophallaxis-retry requires --task-id"
167
+ return
168
+ fi
169
+
170
+ _immune_ensure_retry_log
171
+
172
+ local tr_log_file
173
+ tr_log_file="$(_immune_retry_log_file)"
174
+ local tr_timestamp
175
+ tr_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
176
+
177
+ # Read current retry count for this task
178
+ local tr_current_count
179
+ tr_current_count=$(jq -r --arg tid "$tr_task_id" \
180
+ '.tasks[$tid].retry_count // 0' "$tr_log_file" 2>/dev/null || echo "0")
181
+ local tr_new_count=$(( tr_current_count + 1 ))
182
+
183
+ # Validate diagnosis JSON (graceful: use empty object if invalid)
184
+ local tr_diag_json
185
+ if echo "$tr_diagnosis" | jq empty 2>/dev/null; then
186
+ tr_diag_json="$tr_diagnosis"
187
+ else
188
+ tr_diag_json="{}"
189
+ fi
190
+
191
+ # Build the retry entry
192
+ local tr_entry
193
+ tr_entry=$(jq -n \
194
+ --arg ts "$tr_timestamp" \
195
+ --argjson retry_count "$tr_new_count" \
196
+ --argjson diagnosis "$tr_diag_json" \
197
+ '{timestamp: $ts, retry_count: $retry_count, diagnosis: $diagnosis}')
198
+
199
+ # Update log with locking
200
+ local tr_updated
201
+ if acquire_lock "$tr_log_file" 2>/dev/null; then
202
+ tr_updated=$(jq \
203
+ --arg tid "$tr_task_id" \
204
+ --argjson entry "$tr_entry" \
205
+ --argjson new_count "$tr_new_count" \
206
+ '.tasks[$tid] = {
207
+ retry_count: $new_count,
208
+ last_attempt: $entry.timestamp,
209
+ last_diagnosis: $entry.diagnosis,
210
+ history: ((.tasks[$tid].history // []) + [$entry])
211
+ }' "$tr_log_file" 2>/dev/null)
212
+
213
+ if [[ -n "$tr_updated" ]]; then
214
+ atomic_write "$tr_log_file" "$tr_updated"
215
+ fi
216
+ release_lock 2>/dev/null || true
217
+ else
218
+ # Lockless fallback
219
+ tr_updated=$(jq \
220
+ --arg tid "$tr_task_id" \
221
+ --argjson entry "$tr_entry" \
222
+ --argjson new_count "$tr_new_count" \
223
+ '.tasks[$tid] = {
224
+ retry_count: $new_count,
225
+ last_attempt: $entry.timestamp,
226
+ last_diagnosis: $entry.diagnosis,
227
+ history: ((.tasks[$tid].history // []) + [$entry])
228
+ }' "$tr_log_file" 2>/dev/null)
229
+
230
+ if [[ -n "$tr_updated" ]]; then
231
+ atomic_write "$tr_log_file" "$tr_updated"
232
+ fi
233
+ fi
234
+
235
+ json_ok "$(jq -n \
236
+ --arg task_id "$tr_task_id" \
237
+ --argjson retry_count "$tr_new_count" \
238
+ '{task_id: $task_id, retry_count: $retry_count, diagnosis_injected: true}')"
239
+ }
240
+
241
+ # ---------------------------------------------------------------------------
242
+ # _scar_add
243
+ # Usage: scar-add --pattern <desc> --severity <low|medium|high> [--phase N] [--source <src>]
244
+ # ---------------------------------------------------------------------------
245
+ _scar_add() {
246
+ local sa_pattern=""
247
+ local sa_severity=""
248
+ local sa_phase=""
249
+ local sa_source="unknown"
250
+
251
+ while [[ $# -gt 0 ]]; do
252
+ case "$1" in
253
+ --pattern) sa_pattern="${2:-}"; shift 2 ;;
254
+ --severity) sa_severity="${2:-}"; shift 2 ;;
255
+ --phase) sa_phase="${2:-}"; shift 2 ;;
256
+ --source) sa_source="${2:-}"; shift 2 ;;
257
+ *) shift ;;
258
+ esac
259
+ done
260
+
261
+ if [[ -z "$sa_pattern" ]]; then
262
+ json_err "$E_VALIDATION_FAILED" "scar-add requires --pattern"
263
+ return
264
+ fi
265
+
266
+ # Default severity if not provided
267
+ if [[ -z "$sa_severity" ]]; then
268
+ sa_severity="medium"
269
+ fi
270
+
271
+ # Validate severity
272
+ case "$sa_severity" in
273
+ low|medium|high) ;;
274
+ *)
275
+ json_err "$E_VALIDATION_FAILED" "scar-add --severity must be low, medium, or high"
276
+ return
277
+ ;;
278
+ esac
279
+
280
+ _immune_ensure_scars_file
281
+
282
+ local sa_scars_file
283
+ sa_scars_file="$(_immune_scars_file)"
284
+ local sa_timestamp
285
+ sa_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
286
+ local sa_id
287
+ sa_id="scar_$(date +%s)_$$"
288
+
289
+ # Build phase value for JSON
290
+ local sa_phase_val
291
+ if [[ -n "$sa_phase" ]]; then
292
+ sa_phase_val="$sa_phase"
293
+ else
294
+ sa_phase_val="null"
295
+ fi
296
+
297
+ local sa_new_scar
298
+ sa_new_scar=$(jq -n \
299
+ --arg id "$sa_id" \
300
+ --arg pattern "$sa_pattern" \
301
+ --arg severity "$sa_severity" \
302
+ --arg phase "$sa_phase_val" \
303
+ --arg source "$sa_source" \
304
+ --arg created_at "$sa_timestamp" \
305
+ '{
306
+ id: $id,
307
+ pattern: $pattern,
308
+ severity: $severity,
309
+ phase: (if $phase == "null" then null else ($phase | tonumber? // $phase) end),
310
+ source: $source,
311
+ created_at: $created_at,
312
+ retry_count: 0,
313
+ active: true
314
+ }')
315
+
316
+ local sa_updated
317
+ if acquire_lock "$sa_scars_file" 2>/dev/null; then
318
+ sa_updated=$(jq \
319
+ --argjson scar "$sa_new_scar" \
320
+ '.scars += [$scar]' "$sa_scars_file" 2>/dev/null)
321
+ if [[ -n "$sa_updated" ]]; then
322
+ atomic_write "$sa_scars_file" "$sa_updated"
323
+ fi
324
+ release_lock 2>/dev/null || true
325
+ else
326
+ sa_updated=$(jq \
327
+ --argjson scar "$sa_new_scar" \
328
+ '.scars += [$scar]' "$sa_scars_file" 2>/dev/null)
329
+ if [[ -n "$sa_updated" ]]; then
330
+ atomic_write "$sa_scars_file" "$sa_updated"
331
+ fi
332
+ fi
333
+
334
+ local sa_count
335
+ sa_count=$(jq '[.scars[]] | length' "$sa_scars_file" 2>/dev/null || echo "1")
336
+
337
+ json_ok "$(jq -n \
338
+ --arg id "$sa_id" \
339
+ --argjson scar_count "$sa_count" \
340
+ '{id: $id, scar_count: $scar_count}')"
341
+ }
342
+
343
+ # ---------------------------------------------------------------------------
344
+ # _scar_list
345
+ # Usage: scar-list [--active] [--severity <level>]
346
+ # ---------------------------------------------------------------------------
347
+ _scar_list() {
348
+ local sl_active_only=false
349
+ local sl_severity=""
350
+
351
+ while [[ $# -gt 0 ]]; do
352
+ case "$1" in
353
+ --active) sl_active_only=true; shift ;;
354
+ --severity) sl_severity="${2:-}"; shift 2 ;;
355
+ *) shift ;;
356
+ esac
357
+ done
358
+
359
+ local sl_scars_file
360
+ sl_scars_file="$(_immune_scars_file)"
361
+
362
+ if [[ ! -f "$sl_scars_file" ]]; then
363
+ json_ok '{"total":0,"active":0,"scars":[]}'
364
+ return 0
365
+ fi
366
+
367
+ local sl_result
368
+ sl_result=$(jq \
369
+ --argjson active_only "$sl_active_only" \
370
+ --arg severity "$sl_severity" \
371
+ '
372
+ [.scars // [] | .[] |
373
+ if $active_only then select(.active == true) else . end |
374
+ if ($severity | length) > 0 then select(.severity == $severity) else . end
375
+ ] |
376
+ . as $filtered |
377
+ {
378
+ total: ($filtered | length),
379
+ active: ($filtered | map(select(.active == true)) | length),
380
+ scars: $filtered
381
+ }
382
+ ' "$sl_scars_file" 2>/dev/null)
383
+
384
+ if [[ -z "$sl_result" ]]; then
385
+ json_ok '{"total":0,"active":0,"scars":[]}'
386
+ return 0
387
+ fi
388
+
389
+ json_ok "$sl_result"
390
+ }
391
+
392
+ # ---------------------------------------------------------------------------
393
+ # _scar_check
394
+ # Usage: scar-check --task <desc>
395
+ # ---------------------------------------------------------------------------
396
+ _scar_check() {
397
+ local sc_task=""
398
+
399
+ while [[ $# -gt 0 ]]; do
400
+ case "$1" in
401
+ --task) sc_task="${2:-}"; shift 2 ;;
402
+ *) shift ;;
403
+ esac
404
+ done
405
+
406
+ if [[ -z "$sc_task" ]]; then
407
+ json_err "$E_VALIDATION_FAILED" "scar-check requires --task"
408
+ return
409
+ fi
410
+
411
+ local sc_scars_file
412
+ sc_scars_file="$(_immune_scars_file)"
413
+
414
+ if [[ ! -f "$sc_scars_file" ]]; then
415
+ json_ok '{"matches":0,"scars":[]}'
416
+ return 0
417
+ fi
418
+
419
+ local sc_result
420
+ sc_result=$(jq \
421
+ --arg task "$sc_task" \
422
+ '
423
+ [.scars // [] | .[] |
424
+ select(.active == true) |
425
+ # Split pattern into words, check if any word from pattern appears in task
426
+ select(
427
+ (.pattern | ascii_downcase) as $pat |
428
+ ($pat | split(" ") | .[] | select(length >= 3)) as $word |
429
+ ($task | ascii_downcase) | contains($word)
430
+ )
431
+ ] |
432
+ . as $matches |
433
+ {
434
+ matches: ($matches | length),
435
+ scars: $matches
436
+ }
437
+ ' "$sc_scars_file" 2>/dev/null)
438
+
439
+ if [[ -z "$sc_result" ]]; then
440
+ json_ok '{"matches":0,"scars":[]}'
441
+ return 0
442
+ fi
443
+
444
+ json_ok "$sc_result"
445
+ }
446
+
447
+ # ---------------------------------------------------------------------------
448
+ # _immune_auto_scar
449
+ # Usage: immune-auto-scar --task-id <id>
450
+ # ---------------------------------------------------------------------------
451
+ _immune_auto_scar() {
452
+ local ias_task_id=""
453
+
454
+ while [[ $# -gt 0 ]]; do
455
+ case "$1" in
456
+ --task-id) ias_task_id="${2:-}"; shift 2 ;;
457
+ *) shift ;;
458
+ esac
459
+ done
460
+
461
+ if [[ -z "$ias_task_id" ]]; then
462
+ json_err "$E_VALIDATION_FAILED" "immune-auto-scar requires --task-id"
463
+ return
464
+ fi
465
+
466
+ local ias_log_file
467
+ ias_log_file="$(_immune_retry_log_file)"
468
+
469
+ if [[ ! -f "$ias_log_file" ]]; then
470
+ json_ok '{"auto_scarred":false,"retry_count":0}'
471
+ return 0
472
+ fi
473
+
474
+ local ias_retry_count
475
+ ias_retry_count=$(jq -r --arg tid "$ias_task_id" \
476
+ '.tasks[$tid].retry_count // 0' "$ias_log_file" 2>/dev/null || echo "0")
477
+
478
+ if [[ "$ias_retry_count" -lt 3 ]]; then
479
+ json_ok "$(jq -n \
480
+ --argjson retry_count "$ias_retry_count" \
481
+ '{auto_scarred: false, retry_count: $retry_count}')"
482
+ return 0
483
+ fi
484
+
485
+ # Auto-create a scar for this task
486
+ local ias_last_diagnosis
487
+ ias_last_diagnosis=$(jq -r --arg tid "$ias_task_id" \
488
+ '.tasks[$tid].last_diagnosis.failure // ""' "$ias_log_file" 2>/dev/null || echo "")
489
+
490
+ local ias_pattern
491
+ if [[ -n "$ias_last_diagnosis" ]]; then
492
+ ias_pattern="$ias_last_diagnosis"
493
+ else
494
+ ias_pattern="task $ias_task_id failed persistently (auto-scarred after $ias_retry_count retries)"
495
+ fi
496
+
497
+ # Determine severity based on retry count
498
+ local ias_severity="medium"
499
+ if [[ "$ias_retry_count" -ge 5 ]]; then
500
+ ias_severity="high"
501
+ fi
502
+
503
+ _scar_add --pattern "$ias_pattern" --severity "$ias_severity" --source "immune-auto-scar" >/dev/null
504
+
505
+ json_ok "$(jq -n \
506
+ --argjson retry_count "$ias_retry_count" \
507
+ '{auto_scarred: true, retry_count: $retry_count}')"
508
+ }
@@ -431,11 +431,11 @@ _learning_promote_auto() {
431
431
  # SUPPRESS:OK -- read-default: returns fallback on failure
432
432
  --evidence "Auto-promoted after $observation_count observations (confidence: $lp_confidence)" 2>/dev/null \
433
433
  || _aether_log_error "Could not create instinct from promoted learning"
434
- json_ok "$(jq -n --argjson pt "$policy_threshold" --argjson oc "$observation_count" --argjson cc "$colony_count" --arg et "$event_type" '{promoted: true, mode: "auto", policy_threshold: $pt, observation_count: $oc, colony_count: $cc, event_type: $et}')"
434
+ json_ok "$(jq -nc --argjson pt "$policy_threshold" --argjson oc "$observation_count" --argjson cc "$colony_count" --arg et "$event_type" '{promoted: true, mode: "auto", policy_threshold: $pt, observation_count: $oc, colony_count: $cc, event_type: $et}')"
435
435
  else
436
436
  # SUPPRESS:OK -- read-default: query may return empty
437
437
  promote_msg=$(echo "$promote_result" | jq -r '.error.message // "promotion_failed"' 2>/dev/null || echo "promotion_failed")
438
- result=$(jq -n \
438
+ result=$(jq -nc \
439
439
  --arg reason "promotion_failed" \
440
440
  --arg message "$promote_msg" \
441
441
  --argjson policy_threshold "$policy_threshold" \
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # Merge Driver: npm lockfile auto-merge
3
+ #
4
+ # Resolves package-lock.json merge conflicts by keeping "ours" (the branch
5
+ # being merged into). Lockfiles are deterministic outputs of package.json,
6
+ # so the correct resolution is always to regenerate from the target branch's
7
+ # package.json, which effectively means keeping "ours".
8
+ #
9
+ # Usage: merge-driver-lockfile.sh %O %A %B
10
+ # %O = ancestor (base version)
11
+ # %A = ours (current branch, the version we want to keep)
12
+ # %B = theirs (incoming branch)
13
+ #
14
+ # Git merge driver contract:
15
+ # Exit 0 = conflict resolved (merge continues)
16
+ # Exit non-zero = conflict unresolved
17
+ #
18
+ # This driver is configured via:
19
+ # git config merge.lockfile.name "npm lockfile auto-merge"
20
+ # git config merge.lockfile.driver "bash .aether/utils/merge-driver-lockfile.sh %O %A %B"
21
+
22
+ set -euo pipefail
23
+
24
+ ANCESTOR="${1:-}"
25
+ OURS="${2:-}"
26
+ THEIRS="${3:-}"
27
+
28
+ # Strategy: keep "ours" unchanged.
29
+ # The "ours" file already contains the correct content on disk.
30
+ # We do nothing -- git considers the file resolved when we exit 0.
31
+ #
32
+ # Log for debugging (to stderr so it does not pollute merge output)
33
+ echo "[aether] merge-driver: resolved package-lock.json conflict (kept ours)" >&2
34
+
35
+ exit 0