aether-colony 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/.aether/CONTEXT.md +160 -0
  2. package/.aether/QUEEN.md +84 -0
  3. package/.aether/aether-utils.sh +7749 -0
  4. package/.aether/docs/QUEEN-SYSTEM.md +211 -0
  5. package/.aether/docs/README.md +68 -0
  6. package/.aether/docs/caste-system.md +48 -0
  7. package/.aether/docs/disciplines/DISCIPLINES.md +93 -0
  8. package/.aether/docs/disciplines/coding-standards.md +197 -0
  9. package/.aether/docs/disciplines/debugging.md +207 -0
  10. package/.aether/docs/disciplines/learning.md +254 -0
  11. package/.aether/docs/disciplines/tdd.md +257 -0
  12. package/.aether/docs/disciplines/verification-loop.md +167 -0
  13. package/.aether/docs/disciplines/verification.md +116 -0
  14. package/.aether/docs/error-codes.md +268 -0
  15. package/.aether/docs/known-issues.md +233 -0
  16. package/.aether/docs/pheromones.md +205 -0
  17. package/.aether/docs/queen-commands.md +97 -0
  18. package/.aether/exchange/colony-registry.xml +11 -0
  19. package/.aether/exchange/pheromone-xml.sh +575 -0
  20. package/.aether/exchange/pheromones.xml +87 -0
  21. package/.aether/exchange/queen-wisdom.xml +14 -0
  22. package/.aether/exchange/registry-xml.sh +273 -0
  23. package/.aether/exchange/wisdom-xml.sh +319 -0
  24. package/.aether/midden/approach-changes.md +5 -0
  25. package/.aether/midden/build-failures.md +5 -0
  26. package/.aether/midden/test-failures.md +5 -0
  27. package/.aether/model-profiles.yaml +100 -0
  28. package/.aether/rules/aether-colony.md +134 -0
  29. package/.aether/schemas/aether-types.xsd +255 -0
  30. package/.aether/schemas/colony-registry.xsd +309 -0
  31. package/.aether/schemas/example-prompt-builder.xml +234 -0
  32. package/.aether/schemas/pheromone.xsd +163 -0
  33. package/.aether/schemas/prompt.xsd +416 -0
  34. package/.aether/schemas/queen-wisdom.xsd +325 -0
  35. package/.aether/schemas/worker-priming.xsd +276 -0
  36. package/.aether/templates/QUEEN.md.template +79 -0
  37. package/.aether/templates/colony-state-reset.jq.template +22 -0
  38. package/.aether/templates/colony-state.template.json +35 -0
  39. package/.aether/templates/constraints.template.json +9 -0
  40. package/.aether/templates/crowned-anthill.template.md +36 -0
  41. package/.aether/templates/handoff-build-error.template.md +30 -0
  42. package/.aether/templates/handoff-build-success.template.md +39 -0
  43. package/.aether/templates/handoff.template.md +40 -0
  44. package/.aether/templates/learning-observations.template.json +6 -0
  45. package/.aether/templates/midden.template.json +7 -0
  46. package/.aether/templates/pheromones.template.json +6 -0
  47. package/.aether/templates/session.template.json +9 -0
  48. package/.aether/utils/atomic-write.sh +219 -0
  49. package/.aether/utils/chamber-compare.sh +193 -0
  50. package/.aether/utils/chamber-utils.sh +297 -0
  51. package/.aether/utils/colorize-log.sh +132 -0
  52. package/.aether/utils/error-handler.sh +212 -0
  53. package/.aether/utils/file-lock.sh +158 -0
  54. package/.aether/utils/queen-to-md.xsl +395 -0
  55. package/.aether/utils/semantic-cli.sh +413 -0
  56. package/.aether/utils/spawn-tree.sh +428 -0
  57. package/.aether/utils/spawn-with-model.sh +56 -0
  58. package/.aether/utils/state-loader.sh +215 -0
  59. package/.aether/utils/swarm-display.sh +268 -0
  60. package/.aether/utils/watch-spawn-tree.sh +253 -0
  61. package/.aether/utils/xml-compose.sh +253 -0
  62. package/.aether/utils/xml-convert.sh +273 -0
  63. package/.aether/utils/xml-core.sh +186 -0
  64. package/.aether/utils/xml-query.sh +201 -0
  65. package/.aether/utils/xml-utils.sh +110 -0
  66. package/.aether/workers.md +765 -0
  67. package/.claude/agents/ant/aether-ambassador.md +264 -0
  68. package/.claude/agents/ant/aether-archaeologist.md +322 -0
  69. package/.claude/agents/ant/aether-auditor.md +266 -0
  70. package/.claude/agents/ant/aether-builder.md +187 -0
  71. package/.claude/agents/ant/aether-chaos.md +268 -0
  72. package/.claude/agents/ant/aether-chronicler.md +304 -0
  73. package/.claude/agents/ant/aether-gatekeeper.md +325 -0
  74. package/.claude/agents/ant/aether-includer.md +373 -0
  75. package/.claude/agents/ant/aether-keeper.md +271 -0
  76. package/.claude/agents/ant/aether-measurer.md +317 -0
  77. package/.claude/agents/ant/aether-probe.md +210 -0
  78. package/.claude/agents/ant/aether-queen.md +325 -0
  79. package/.claude/agents/ant/aether-route-setter.md +173 -0
  80. package/.claude/agents/ant/aether-sage.md +353 -0
  81. package/.claude/agents/ant/aether-scout.md +142 -0
  82. package/.claude/agents/ant/aether-surveyor-disciplines.md +416 -0
  83. package/.claude/agents/ant/aether-surveyor-nest.md +354 -0
  84. package/.claude/agents/ant/aether-surveyor-pathogens.md +288 -0
  85. package/.claude/agents/ant/aether-surveyor-provisions.md +359 -0
  86. package/.claude/agents/ant/aether-tracker.md +265 -0
  87. package/.claude/agents/ant/aether-watcher.md +244 -0
  88. package/.claude/agents/ant/aether-weaver.md +247 -0
  89. package/.claude/commands/ant/archaeology.md +341 -0
  90. package/.claude/commands/ant/build.md +1160 -0
  91. package/.claude/commands/ant/chaos.md +349 -0
  92. package/.claude/commands/ant/colonize.md +270 -0
  93. package/.claude/commands/ant/continue.md +1070 -0
  94. package/.claude/commands/ant/council.md +309 -0
  95. package/.claude/commands/ant/dream.md +265 -0
  96. package/.claude/commands/ant/entomb.md +487 -0
  97. package/.claude/commands/ant/feedback.md +78 -0
  98. package/.claude/commands/ant/flag.md +139 -0
  99. package/.claude/commands/ant/flags.md +155 -0
  100. package/.claude/commands/ant/focus.md +58 -0
  101. package/.claude/commands/ant/help.md +122 -0
  102. package/.claude/commands/ant/history.md +137 -0
  103. package/.claude/commands/ant/init.md +409 -0
  104. package/.claude/commands/ant/interpret.md +267 -0
  105. package/.claude/commands/ant/lay-eggs.md +201 -0
  106. package/.claude/commands/ant/maturity.md +102 -0
  107. package/.claude/commands/ant/memory-details.md +77 -0
  108. package/.claude/commands/ant/migrate-state.md +165 -0
  109. package/.claude/commands/ant/oracle.md +387 -0
  110. package/.claude/commands/ant/organize.md +227 -0
  111. package/.claude/commands/ant/pause-colony.md +247 -0
  112. package/.claude/commands/ant/phase.md +126 -0
  113. package/.claude/commands/ant/plan.md +544 -0
  114. package/.claude/commands/ant/redirect.md +58 -0
  115. package/.claude/commands/ant/resume-colony.md +182 -0
  116. package/.claude/commands/ant/resume.md +363 -0
  117. package/.claude/commands/ant/seal.md +306 -0
  118. package/.claude/commands/ant/status.md +272 -0
  119. package/.claude/commands/ant/swarm.md +361 -0
  120. package/.claude/commands/ant/tunnels.md +425 -0
  121. package/.claude/commands/ant/update.md +209 -0
  122. package/.claude/commands/ant/verify-castes.md +95 -0
  123. package/.claude/commands/ant/watch.md +238 -0
  124. package/.opencode/agents/aether-ambassador.md +140 -0
  125. package/.opencode/agents/aether-archaeologist.md +108 -0
  126. package/.opencode/agents/aether-auditor.md +144 -0
  127. package/.opencode/agents/aether-builder.md +184 -0
  128. package/.opencode/agents/aether-chaos.md +115 -0
  129. package/.opencode/agents/aether-chronicler.md +122 -0
  130. package/.opencode/agents/aether-gatekeeper.md +116 -0
  131. package/.opencode/agents/aether-includer.md +117 -0
  132. package/.opencode/agents/aether-keeper.md +177 -0
  133. package/.opencode/agents/aether-measurer.md +128 -0
  134. package/.opencode/agents/aether-probe.md +133 -0
  135. package/.opencode/agents/aether-queen.md +286 -0
  136. package/.opencode/agents/aether-route-setter.md +130 -0
  137. package/.opencode/agents/aether-sage.md +106 -0
  138. package/.opencode/agents/aether-scout.md +101 -0
  139. package/.opencode/agents/aether-surveyor-disciplines.md +386 -0
  140. package/.opencode/agents/aether-surveyor-nest.md +324 -0
  141. package/.opencode/agents/aether-surveyor-pathogens.md +259 -0
  142. package/.opencode/agents/aether-surveyor-provisions.md +329 -0
  143. package/.opencode/agents/aether-tracker.md +137 -0
  144. package/.opencode/agents/aether-watcher.md +174 -0
  145. package/.opencode/agents/aether-weaver.md +130 -0
  146. package/.opencode/commands/ant/archaeology.md +338 -0
  147. package/.opencode/commands/ant/build.md +1200 -0
  148. package/.opencode/commands/ant/chaos.md +346 -0
  149. package/.opencode/commands/ant/colonize.md +202 -0
  150. package/.opencode/commands/ant/continue.md +938 -0
  151. package/.opencode/commands/ant/council.md +305 -0
  152. package/.opencode/commands/ant/dream.md +262 -0
  153. package/.opencode/commands/ant/entomb.md +367 -0
  154. package/.opencode/commands/ant/feedback.md +80 -0
  155. package/.opencode/commands/ant/flag.md +137 -0
  156. package/.opencode/commands/ant/flags.md +153 -0
  157. package/.opencode/commands/ant/focus.md +56 -0
  158. package/.opencode/commands/ant/help.md +124 -0
  159. package/.opencode/commands/ant/history.md +127 -0
  160. package/.opencode/commands/ant/init.md +337 -0
  161. package/.opencode/commands/ant/interpret.md +256 -0
  162. package/.opencode/commands/ant/lay-eggs.md +141 -0
  163. package/.opencode/commands/ant/maturity.md +92 -0
  164. package/.opencode/commands/ant/memory-details.md +77 -0
  165. package/.opencode/commands/ant/migrate-state.md +153 -0
  166. package/.opencode/commands/ant/oracle.md +338 -0
  167. package/.opencode/commands/ant/organize.md +224 -0
  168. package/.opencode/commands/ant/pause-colony.md +220 -0
  169. package/.opencode/commands/ant/phase.md +123 -0
  170. package/.opencode/commands/ant/plan.md +531 -0
  171. package/.opencode/commands/ant/redirect.md +67 -0
  172. package/.opencode/commands/ant/resume-colony.md +178 -0
  173. package/.opencode/commands/ant/resume.md +363 -0
  174. package/.opencode/commands/ant/seal.md +247 -0
  175. package/.opencode/commands/ant/status.md +272 -0
  176. package/.opencode/commands/ant/swarm.md +357 -0
  177. package/.opencode/commands/ant/tunnels.md +406 -0
  178. package/.opencode/commands/ant/update.md +191 -0
  179. package/.opencode/commands/ant/verify-castes.md +85 -0
  180. package/.opencode/commands/ant/watch.md +220 -0
  181. package/.opencode/opencode.json +3 -0
  182. package/CHANGELOG.md +325 -0
  183. package/DISCLAIMER.md +74 -0
  184. package/LICENSE +21 -0
  185. package/README.md +258 -0
  186. package/bin/cli.js +2436 -0
  187. package/bin/generate-commands.sh +291 -0
  188. package/bin/lib/caste-colors.js +57 -0
  189. package/bin/lib/colors.js +76 -0
  190. package/bin/lib/errors.js +255 -0
  191. package/bin/lib/event-types.js +190 -0
  192. package/bin/lib/file-lock.js +695 -0
  193. package/bin/lib/init.js +454 -0
  194. package/bin/lib/logger.js +242 -0
  195. package/bin/lib/model-profiles.js +445 -0
  196. package/bin/lib/model-verify.js +288 -0
  197. package/bin/lib/nestmate-loader.js +130 -0
  198. package/bin/lib/proxy-health.js +253 -0
  199. package/bin/lib/spawn-logger.js +266 -0
  200. package/bin/lib/state-guard.js +602 -0
  201. package/bin/lib/state-sync.js +516 -0
  202. package/bin/lib/telemetry.js +441 -0
  203. package/bin/lib/update-transaction.js +1454 -0
  204. package/bin/npx-install.js +178 -0
  205. package/bin/sync-to-runtime.sh +6 -0
  206. package/bin/validate-package.sh +88 -0
  207. package/package.json +70 -0
@@ -0,0 +1,22 @@
1
+ # colony-state-reset.jq.template — Reset colony state fields for entomb
2
+ # Usage: jq -f .aether/templates/colony-state-reset.jq.template STATE.json.bak > STATE.json
3
+ # Version: 1.0
4
+
5
+ .goal = null |
6
+ .state = "IDLE" |
7
+ .current_phase = 0 |
8
+ .plan.phases = [] |
9
+ .plan.generated_at = null |
10
+ .plan.confidence = null |
11
+ .build_started_at = null |
12
+ .session_id = null |
13
+ .initialized_at = null |
14
+ .milestone = null |
15
+ .events = [] |
16
+ .errors.records = [] |
17
+ .errors.flagged_patterns = [] |
18
+ .signals = [] |
19
+ .graveyards = [] |
20
+ .memory.instincts = [] |
21
+ .memory.phase_learnings = [] |
22
+ .memory.decisions = []
@@ -0,0 +1,35 @@
1
+ {
2
+ "_template": "colony-state",
3
+ "_version": "3.0",
4
+ "_instructions": "Write this file to .aether/data/COLONY_STATE.json. Replace every __PLACEHOLDER__ value with real data (see _comment_* fields for details). After filling all placeholders, remove every key whose name starts with underscore (_template, _version, _instructions, and all _comment_*) before writing the final file.",
5
+ "_comment_goal": "Replace __GOAL__ with the user's goal string exactly as provided to /ant:init.",
6
+ "_comment_session": "Replace __SESSION_ID__ with a generated ID in the format: session_{unix_timestamp}_{random}. Replace __ISO8601_TIMESTAMP__ with the current UTC time in ISO-8601 format (e.g., 2026-02-20T00:00:00Z). The same timestamp value is used in both 'initialized_at' and the events array entry.",
7
+ "_comment_memory": "Replace __PHASE_LEARNINGS__ with a JSON array (e.g., [] if no prior colony found, or [{...}, ...] if inherited from completion-report.md). Replace __INSTINCTS__ with a JSON array (e.g., [] or [{...}, ...]). IMPORTANT: both must be JSON arrays, not strings — write [] not \"[]\".",
8
+ "_comment_events": "The events array records colony lifecycle events. Each entry is a pipe-delimited string: 'ISO8601_TIMESTAMP|event_type|source|description'. The initialization event uses the same __ISO8601_TIMESTAMP__ as 'initialized_at'. Do not add extra events during init — the single initialization entry is correct.",
9
+ "version": "3.0",
10
+ "goal": "__GOAL__",
11
+ "state": "READY",
12
+ "current_phase": 0,
13
+ "session_id": "__SESSION_ID__",
14
+ "initialized_at": "__ISO8601_TIMESTAMP__",
15
+ "build_started_at": null,
16
+ "plan": {
17
+ "generated_at": null,
18
+ "confidence": null,
19
+ "phases": []
20
+ },
21
+ "memory": {
22
+ "phase_learnings": "__PHASE_LEARNINGS__",
23
+ "decisions": [],
24
+ "instincts": "__INSTINCTS__"
25
+ },
26
+ "errors": {
27
+ "records": [],
28
+ "flagged_patterns": []
29
+ },
30
+ "signals": [],
31
+ "graveyards": [],
32
+ "events": [
33
+ "__ISO8601_TIMESTAMP__|colony_initialized|init|Colony initialized with goal: __GOAL__"
34
+ ]
35
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "_template": "constraints",
3
+ "_version": "1.0",
4
+ "_instructions": "Write this file to .aether/data/constraints.json. No placeholder substitution needed — copy the data keys as-is. Remove all keys whose names start with underscore (_template, _version, _instructions, _comment_*) before writing the final file.",
5
+ "_comment_purpose": "constraints.json stores user pheromone signals. 'focus' holds FOCUS signals (areas of attention set via /ant:focus). 'constraints' holds REDIRECT signals (hard constraints set via /ant:redirect). Both start as empty arrays and accumulate signals over the colony's lifetime.",
6
+ "version": "1.0",
7
+ "focus": [],
8
+ "constraints": []
9
+ }
@@ -0,0 +1,36 @@
1
+ <!-- Template: crowned-anthill | Version: 2.0 -->
2
+ <!-- Instructions: Fill all {{PLACEHOLDER}} values with real colony data. Remove this comment block before writing to .aether/CROWNED-ANTHILL.md -->
3
+
4
+ # Crowned Anthill — {{GOAL}}
5
+
6
+ **Sealed:** {{SEAL_DATE}}
7
+ **Milestone:** Crowned Anthill
8
+ **Version:** {{VERSION}}
9
+
10
+ ## The Achievement
11
+
12
+ This colony set out to accomplish something real — and it did. Every phase pushed the anthill higher. Now it stands crowned.
13
+
14
+ ## Colony Stats
15
+ - Total Phases: {{TOTAL_PHASES}}
16
+ - Phases Completed: {{PHASES_COMPLETED}} of {{TOTAL_PHASES}}
17
+ - Colony Age: {{COLONY_AGE_DAYS}} days of focused work
18
+ - Wisdom Promoted: {{PROMOTIONS_MADE}} entries carried forward
19
+
20
+ ## Phase Recap
21
+
22
+ Every phase below is a chapter in the story of this anthill's rise:
23
+
24
+ {{PHASE_RECAP}}
25
+
26
+ ## Pheromone Legacy
27
+
28
+ The colony's hard-won wisdom doesn't stop here. {{PROMOTIONS_MADE}} validated learnings and instincts have been promoted to QUEEN.md — a living record that will guide future colonies before they take their first steps.
29
+
30
+ What this colony learned, the next colony inherits.
31
+
32
+ ## The Work
33
+
34
+ {{GOAL}}
35
+
36
+ The anthill stands crowned. The work endures.
@@ -0,0 +1,30 @@
1
+ <!-- Template: handoff-build-error | Version: 1.0 -->
2
+ <!-- Instructions: Fill all {{PLACEHOLDER}} values with real colony data. Remove this comment block before writing to .aether/HANDOFF.md -->
3
+
4
+ # Colony Session — Build Errors
5
+
6
+ ## Build Status: ISSUES DETECTED
7
+
8
+ **Phase:** {{PHASE_NUMBER}} — {{PHASE_NAME}}
9
+ **Status:** Build completed with failures
10
+ **Updated:** {{BUILD_TIMESTAMP}}
11
+
12
+ ## Failed Workers
13
+
14
+ {{FAILED_WORKERS}}
15
+
16
+ ## Grave Markers Placed
17
+
18
+ {{GRAVE_MARKERS}}
19
+
20
+ ## Recovery Options
21
+
22
+ 1. Review failures: Check `.aether/data/activity.log`
23
+ 2. Fix and retry: `/ant:build {{PHASE_NUMBER}}`
24
+ 3. Swarm fix: `/ant:swarm` for auto-repair
25
+ 4. Manual fix: Address issues, then `/ant:continue`
26
+
27
+ ## Session Note
28
+
29
+ Build completed but workers failed. Grave markers placed.
30
+ Review failures before advancing.
@@ -0,0 +1,39 @@
1
+ <!-- Template: handoff-build-success | Version: 1.0 -->
2
+ <!-- Instructions: Fill all {{PLACEHOLDER}} values with real colony data. Remove this comment block before writing to .aether/HANDOFF.md -->
3
+
4
+ # Colony Session — Build Complete
5
+
6
+ ## Quick Resume
7
+
8
+ Run `/ant:continue` to advance phase, or `/ant:resume-colony` to restore full context.
9
+
10
+ ## State at Build Completion
11
+
12
+ - Goal: "{{GOAL}}"
13
+ - Phase: {{PHASE_NUMBER}} — {{PHASE_NAME}}
14
+ - Build Status: {{BUILD_STATUS}}
15
+ - Updated: {{BUILD_TIMESTAMP}}
16
+
17
+ ## Build Summary
18
+
19
+ {{BUILD_SUMMARY}}
20
+
21
+ ## Tasks
22
+
23
+ - Completed: {{TASKS_COMPLETED}}
24
+ - Failed: {{TASKS_FAILED}}
25
+
26
+ ## Files Changed
27
+
28
+ - Created: {{FILES_CREATED}} files
29
+ - Modified: {{FILES_MODIFIED}} files
30
+
31
+ ## Next Steps
32
+
33
+ - If verification passed: `/ant:continue` to advance to next phase
34
+ - If issues found: `/ant:flags` to review blockers
35
+ - To pause: `/ant:pause-colony`
36
+
37
+ ## Session Note
38
+
39
+ {{SESSION_NOTE}}
@@ -0,0 +1,40 @@
1
+ <!-- Template: handoff | Version: 2.0 -->
2
+ <!-- Instructions: Fill all {{PLACEHOLDER}} values with real colony data. Remove this comment block before writing to .aether/HANDOFF.md -->
3
+
4
+ # Colony Session — {{CHAMBER_NAME}}
5
+
6
+ ## A Colony's Rest
7
+
8
+ This colony has been entombed. Its work is complete, its story told.
9
+ What began with a goal has ended with a chamber — a place of quiet preservation.
10
+
11
+ **Chamber:** .aether/chambers/{{CHAMBER_NAME}}/
12
+
13
+ ## Colony Summary
14
+
15
+ - Goal: "{{GOAL}}"
16
+ - Phases: {{PHASES_COMPLETED}} completed of {{TOTAL_PHASES}}
17
+ - Milestone reached: {{MILESTONE}}
18
+ - Entombed at: {{ENTOMB_TIMESTAMP}}
19
+
20
+ ## Chamber Contents
21
+
22
+ Everything the colony built and learned rests here now:
23
+
24
+ - colony-state.json — Full colony state
25
+ - manifest.json — Archive metadata
26
+ - CROWNED-ANTHILL.md — Seal ceremony record
27
+ - pheromones.json — Pheromone signals
28
+ - activity.log — Colony activity history
29
+ - spawn-tree.txt — Worker spawn records
30
+ - dreams/ — Dream journal (if existed)
31
+
32
+ ## Session Note
33
+
34
+ The active colony has been reset. The chambers hold what was.
35
+ The learnings have been carried forward to QUEEN.md — the next colony won't start from nothing.
36
+
37
+ When you are ready to begin again:
38
+
39
+ To start anew: /ant:lay-eggs "<new goal>"
40
+ To revisit what came before: /ant:tunnels
@@ -0,0 +1,6 @@
1
+ {
2
+ "_template": "learning-observations",
3
+ "_version": "1.0",
4
+ "_instructions": "Write to .aether/data/learning-observations.json. No substitution needed. Remove underscore-prefixed keys.",
5
+ "observations": []
6
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "_template": "midden",
3
+ "_version": "1.0",
4
+ "_instructions": "Write to .aether/data/midden/midden.json. No substitution needed. Remove underscore-prefixed keys.",
5
+ "version": "1.0.0",
6
+ "signals": []
7
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_template": "pheromones",
3
+ "_version": "1.0",
4
+ "_instructions": "Write to .aether/data/pheromones.json. No substitution needed. Remove underscore-prefixed keys.",
5
+ "signals": []
6
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "_template": "session",
3
+ "_version": "1.0",
4
+ "_instructions": "Write to .aether/data/session.json. Replace __SESSION_ID__, __GOAL__, __TIMESTAMP__. Remove underscore-prefixed keys.",
5
+ "session_id": "__SESSION_ID__",
6
+ "colony_goal": "__GOAL__",
7
+ "started_at": "__TIMESTAMP__",
8
+ "last_activity": "__TIMESTAMP__"
9
+ }
@@ -0,0 +1,219 @@
1
+ #!/bin/bash
2
+ # Aether Atomic Write Utility
3
+ # Implements atomic write pattern (temp file + rename) for corruption safety
4
+ #
5
+ # Usage:
6
+ # source .aether/utils/atomic-write.sh
7
+ # atomic_write /path/to/file.json "content"
8
+ # atomic_write_from_file /path/to/target.json /path/to/temp.json
9
+
10
+ # Source required utilities
11
+ # Get the directory where this script is located
12
+ _AETHER_UTILS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ # If BASH_SOURCE[0] is empty (can happen in some contexts), use repo-local path
14
+ if [ -z "$_AETHER_UTILS_DIR" ] || [ "$_AETHER_UTILS_DIR" = "$(pwd)" ]; then
15
+ if git rev-parse --show-toplevel >/dev/null 2>&1; then
16
+ _AETHER_UTILS_DIR="$(git rev-parse --show-toplevel)/.aether/utils"
17
+ else
18
+ _AETHER_UTILS_DIR="$PWD/.aether/utils"
19
+ fi
20
+ fi
21
+ # Verify the path exists and file-lock.sh is there
22
+ if [ ! -f "$_AETHER_UTILS_DIR/file-lock.sh" ]; then
23
+ # Try one more fallback - relative to script location
24
+ _AETHER_UTILS_DIR="$(dirname "${BASH_SOURCE[0]}")"
25
+ fi
26
+ source "$_AETHER_UTILS_DIR/file-lock.sh"
27
+
28
+ # Aether root detection - use git root if available, otherwise use current directory
29
+ if git rev-parse --show-toplevel >/dev/null 2>&1; then
30
+ AETHER_ROOT="$(git rev-parse --show-toplevel)"
31
+ else
32
+ AETHER_ROOT="$(pwd)"
33
+ fi
34
+
35
+ TEMP_DIR="$AETHER_ROOT/.aether/temp"
36
+ BACKUP_DIR="$AETHER_ROOT/.aether/data/backups"
37
+
38
+ # Create directories
39
+ mkdir -p "$TEMP_DIR" "$BACKUP_DIR"
40
+
41
+ # Number of backups to keep
42
+ MAX_BACKUPS=3
43
+
44
+ # Atomic write: write content to file via temporary file
45
+ # Arguments: target_file, content
46
+ # Returns: 0 on success, 1 on failure
47
+ atomic_write() {
48
+ local target_file="$1"
49
+ local content="$2"
50
+
51
+ # Ensure target directory exists
52
+ local target_dir=$(dirname "$target_file")
53
+ mkdir -p "$target_dir"
54
+
55
+ # Create unique temp file
56
+ local temp_file="${TEMP_DIR}/$(basename "$target_file").$$.$(date +%s%N).tmp"
57
+
58
+ # Write content to temp file
59
+ if ! echo "$content" > "$temp_file"; then
60
+ echo "Failed to write to temp file: $temp_file"
61
+ rm -f "$temp_file"
62
+ return 1
63
+ fi
64
+
65
+ # Create backup if target exists (do this BEFORE validation to avoid race condition)
66
+ if [ -f "$target_file" ]; then
67
+ create_backup "$target_file"
68
+ fi
69
+
70
+ # Validate JSON if it's a JSON file
71
+ if [[ "$target_file" == *.json ]]; then
72
+ if ! python3 -c "import json; json.load(open('$temp_file'))" 2>/dev/null; then
73
+ echo "Invalid JSON in temp file: $temp_file"
74
+ rm -f "$temp_file"
75
+ return 1
76
+ fi
77
+ fi
78
+
79
+ # Atomic rename (overwrites target if exists)
80
+ if ! mv "$temp_file" "$target_file"; then
81
+ echo "Failed to rename temp file to target: $target_file"
82
+ rm -f "$temp_file"
83
+ return 1
84
+ fi
85
+
86
+ # Sync to disk
87
+ if command -v sync >/dev/null 2>&1; then
88
+ sync "$target_file" 2>/dev/null || true
89
+ fi
90
+
91
+ return 0
92
+ }
93
+
94
+ # Atomic write from source file to target
95
+ # Arguments: target_file, source_file
96
+ # Returns: 0 on success, 1 on failure
97
+ atomic_write_from_file() {
98
+ local target_file="$1"
99
+ local source_file="$2"
100
+
101
+ if [ ! -f "$source_file" ]; then
102
+ echo "Source file does not exist: $source_file"
103
+ return 1
104
+ fi
105
+
106
+ # Ensure target directory exists
107
+ local target_dir=$(dirname "$target_file")
108
+ mkdir -p "$target_dir"
109
+
110
+ # Create unique temp file
111
+ local temp_file="${TEMP_DIR}/$(basename "$target_file").$$.$(date +%s%N).tmp"
112
+
113
+ # Copy source to temp
114
+ if ! cp "$source_file" "$temp_file"; then
115
+ echo "Failed to copy source to temp: $source_file -> $temp_file"
116
+ rm -f "$temp_file"
117
+ return 1
118
+ fi
119
+
120
+ # Create backup BEFORE validation (mirrors atomic_write ordering — LOCK-03)
121
+ if [ -f "$target_file" ]; then
122
+ create_backup "$target_file"
123
+ fi
124
+
125
+ # Validate JSON if it's a JSON file
126
+ if [[ "$target_file" == *.json ]]; then
127
+ if ! python3 -c "import json; json.load(open('$temp_file'))" 2>/dev/null; then
128
+ echo "Invalid JSON in temp file: $temp_file"
129
+ rm -f "$temp_file"
130
+ return 1
131
+ fi
132
+ fi
133
+
134
+ # Atomic rename
135
+ if ! mv "$temp_file" "$target_file"; then
136
+ echo "Failed to rename temp file to target: $target_file"
137
+ rm -f "$temp_file"
138
+ return 1
139
+ fi
140
+
141
+ # Sync to disk
142
+ if command -v sync >/dev/null 2>&1; then
143
+ sync "$target_file" 2>/dev/null || true
144
+ fi
145
+
146
+ return 0
147
+ }
148
+
149
+ # Create backup of file
150
+ # Arguments: file_path
151
+ create_backup() {
152
+ local file_path="$1"
153
+ local base_name=$(basename "$file_path")
154
+ local timestamp=$(date +%Y%m%d_%H%M%S)
155
+ local backup_file="${BACKUP_DIR}/${base_name}.${timestamp}.backup"
156
+
157
+ cp "$file_path" "$backup_file" 2>/dev/null || return 1
158
+
159
+ # Rotate old backups
160
+ rotate_backups "$base_name"
161
+
162
+ return 0
163
+ }
164
+
165
+ # Rotate backups, keeping only MAX_BACKUPS
166
+ # Arguments: base_name
167
+ rotate_backups() {
168
+ local base_name="$1"
169
+ local backups=$(ls -t "${BACKUP_DIR}/${base_name}".*.backup 2>/dev/null | wc -l)
170
+
171
+ if [ "$backups" -gt "$MAX_BACKUPS" ]; then
172
+ ls -t "${BACKUP_DIR}/${base_name}".*.backup 2>/dev/null \
173
+ | tail -n +$((MAX_BACKUPS + 1)) \
174
+ | while IFS= read -r file; do rm -f "$file"; done
175
+ fi
176
+ }
177
+
178
+ # Restore from backup
179
+ # Arguments: target_file, [backup_number]
180
+ # Returns: 0 on success, 1 on failure
181
+ restore_backup() {
182
+ local target_file="$1"
183
+ local backup_num="${2:-1}" # Default to most recent backup
184
+ local base_name=$(basename "$target_file")
185
+
186
+ local backup_file=$(ls -t "${BACKUP_DIR}/${base_name}".*.backup 2>/dev/null | sed -n "${backup_num}p")
187
+
188
+ if [ -z "$backup_file" ] || [ ! -f "$backup_file" ]; then
189
+ echo "No backup found for: $target_file"
190
+ return 1
191
+ fi
192
+
193
+ if ! atomic_write_from_file "$target_file" "$backup_file"; then
194
+ echo "Failed to restore from backup: $backup_file"
195
+ return 1
196
+ fi
197
+
198
+ echo "Restored from: $backup_file"
199
+ return 0
200
+ }
201
+
202
+ # List available backups
203
+ # Arguments: file_path
204
+ list_backups() {
205
+ local file_path="$1"
206
+ local base_name=$(basename "$file_path")
207
+
208
+ echo "Available backups for $base_name:"
209
+ ls -lh "${BACKUP_DIR}/${base_name}".*.backup 2>/dev/null || echo "No backups found"
210
+ }
211
+
212
+ # Cleanup temp files older than 1 hour
213
+ cleanup_temp_files() {
214
+ find "$TEMP_DIR" -name "*.tmp" -mtime +1/24 -delete 2>/dev/null || true
215
+ }
216
+
217
+ # Export functions
218
+ export -f atomic_write atomic_write_from_file create_backup rotate_backups
219
+ export -f restore_backup list_backups cleanup_temp_files
@@ -0,0 +1,193 @@
1
+ #!/bin/bash
2
+ # Chamber comparison utilities
3
+ # Usage: bash chamber-compare.sh <chamber_a> <chamber_b>
4
+
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ CHAMBERS_DIR="${CHAMBERS_DIR:-.aether/chambers}"
9
+
10
+ # Source error-handler.sh for E_* constants and enhanced json_err
11
+ [[ -f "$SCRIPT_DIR/error-handler.sh" ]] && source "$SCRIPT_DIR/error-handler.sh"
12
+
13
+ # Fallback E_* constants (no-ops when error-handler.sh is already loaded)
14
+ : "${E_UNKNOWN:=E_UNKNOWN}"
15
+ : "${E_FILE_NOT_FOUND:=E_FILE_NOT_FOUND}"
16
+ : "${E_VALIDATION_FAILED:=E_VALIDATION_FAILED}"
17
+
18
+ # JSON output helpers
19
+ json_ok() { printf '{"ok":true,"result":%s}\n' "$1"; }
20
+
21
+ # Guard: yield to error-handler.sh's enhanced json_err when already loaded
22
+ if ! type json_err &>/dev/null; then
23
+ json_err() {
24
+ local code="${1:-E_UNKNOWN}"
25
+ local message="${2:-An unknown error occurred}"
26
+ printf '{"ok":false,"error":{"code":"%s","message":"%s"}}\n' "$code" "$message" >&2
27
+ exit 1
28
+ }
29
+ fi
30
+
31
+ # Load chamber manifest
32
+ load_chamber() {
33
+ local chamber_name="$1"
34
+ local manifest_file="$CHAMBERS_DIR/$chamber_name/manifest.json"
35
+
36
+ if [[ ! -f "$manifest_file" ]]; then
37
+ json_err "$E_FILE_NOT_FOUND" "Chamber not found: $chamber_name. Try: check the chamber name with /ant:tunnels."
38
+ fi
39
+
40
+ cat "$manifest_file"
41
+ }
42
+
43
+ # Compare two chambers
44
+ cmd="${1:-help}"
45
+ shift 2>/dev/null || true
46
+
47
+ case "$cmd" in
48
+ help)
49
+ cat <<'EOF'
50
+ {"ok":true,"commands":["compare","diff","stats"],"description":"Chamber comparison utilities"}
51
+ EOF
52
+ ;;
53
+
54
+ compare)
55
+ chamber_a="${1:-}"
56
+ chamber_b="${2:-}"
57
+ [[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "$E_VALIDATION_FAILED" "Missing arguments. Try: compare <chamber_a> <chamber_b>."
58
+
59
+ # Load both manifests
60
+ manifest_a=$(load_chamber "$chamber_a")
61
+ manifest_b=$(load_chamber "$chamber_b")
62
+
63
+ # Extract key fields for comparison
64
+ result=$(jq -n \
65
+ --arg a_name "$chamber_a" \
66
+ --arg b_name "$chamber_b" \
67
+ --argjson a "$manifest_a" \
68
+ --argjson b "$manifest_b" \
69
+ '{
70
+ chamber_a: {
71
+ name: $a_name,
72
+ goal: $a.goal,
73
+ milestone: $a.milestone,
74
+ version: $a.version,
75
+ phases_completed: $a.phases_completed,
76
+ total_phases: $a.total_phases,
77
+ entombed_at: $a.entombed_at,
78
+ decisions_count: ($a.decisions | length),
79
+ learnings_count: ($a.learnings | length)
80
+ },
81
+ chamber_b: {
82
+ name: $b_name,
83
+ goal: $b.goal,
84
+ milestone: $b.milestone,
85
+ version: $b.version,
86
+ phases_completed: $b.phases_completed,
87
+ total_phases: $b.total_phases,
88
+ entombed_at: $b.entombed_at,
89
+ decisions_count: ($b.decisions | length),
90
+ learnings_count: ($b.learnings | length)
91
+ },
92
+ comparison: {
93
+ phases_diff: ($b.phases_completed - $a.phases_completed),
94
+ decisions_diff: (($b.decisions | length) - ($a.decisions | length)),
95
+ learnings_diff: (($b.learnings | length) - ($a.learnings | length)),
96
+ same_milestone: ($a.milestone == $b.milestone),
97
+ time_between: (
98
+ (($b.entombed_at | fromdateiso8601) - ($a.entombed_at | fromdateiso8601)) / 86400 | floor
99
+ )
100
+ }
101
+ }')
102
+
103
+ json_ok "$result"
104
+ ;;
105
+
106
+ diff)
107
+ chamber_a="${1:-}"
108
+ chamber_b="${2:-}"
109
+ [[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "$E_VALIDATION_FAILED" "Missing arguments. Try: diff <chamber_a> <chamber_b>."
110
+
111
+ manifest_a=$(load_chamber "$chamber_a")
112
+ manifest_b=$(load_chamber "$chamber_b")
113
+
114
+ # Find decisions in B but not in A (new decisions)
115
+ # Find learnings in B but not in A (new learnings)
116
+ result=$(jq -n \
117
+ --arg a_name "$chamber_a" \
118
+ --arg b_name "$chamber_b" \
119
+ --argjson a "$manifest_a" \
120
+ --argjson b "$manifest_b" \
121
+ '{
122
+ new_decisions: [
123
+ $b.decisions[] | select(
124
+ .content as $content |
125
+ $a.decisions | map(.content) | contains([$content]) | not
126
+ )
127
+ ],
128
+ new_learnings: [
129
+ $b.learnings[] | select(
130
+ .content as $content |
131
+ $a.learnings | map(.content) | contains([$content]) | not
132
+ )
133
+ ],
134
+ preserved_decisions: [
135
+ $a.decisions[] | select(
136
+ .content as $content |
137
+ $b.decisions | map(.content) | contains([$content])
138
+ )
139
+ ],
140
+ preserved_learnings: [
141
+ $a.learnings[] | select(
142
+ .content as $content |
143
+ $b.learnings | map(.content) | contains([$content])
144
+ )
145
+ ]
146
+ }')
147
+
148
+ json_ok "$result"
149
+ ;;
150
+
151
+ stats)
152
+ chamber_a="${1:-}"
153
+ chamber_b="${2:-}"
154
+ [[ -z "$chamber_a" || -z "$chamber_b" ]] && json_err "$E_VALIDATION_FAILED" "Missing arguments. Try: stats <chamber_a> <chamber_b>."
155
+
156
+ manifest_a=$(load_chamber "$chamber_a")
157
+ manifest_b=$(load_chamber "$chamber_b")
158
+
159
+ # Calculate detailed statistics
160
+ result=$(jq -n \
161
+ --arg a_name "$chamber_a" \
162
+ --arg b_name "$chamber_b" \
163
+ --argjson a "$manifest_a" \
164
+ --argjson b "$manifest_b" \
165
+ '{
166
+ summary: {
167
+ a_phases: $a.phases_completed,
168
+ b_phases: $b.phases_completed,
169
+ growth: "\($b.phases_completed - $a.phases_completed) phases",
170
+ a_duration_days: null,
171
+ b_duration_days: null
172
+ },
173
+ knowledge_transfer: {
174
+ decisions_preserved: ($a.decisions | map(.content) | intersection($b.decisions | map(.content)) | length),
175
+ decisions_new: ($b.decisions | map(.content) - ($a.decisions | map(.content)) | length),
176
+ learnings_preserved: ($a.learnings | map(.content) | intersection($b.learnings | map(.content)) | length),
177
+ learnings_new: ($b.learnings | map(.content) - ($a.learnings | map(.content)) | length)
178
+ },
179
+ evolution: {
180
+ milestone_changed: ($a.milestone != $b.milestone),
181
+ from_milestone: $a.milestone,
182
+ to_milestone: $b.milestone,
183
+ version_delta: "\($a.version) → \($b.version)"
184
+ }
185
+ }')
186
+
187
+ json_ok "$result"
188
+ ;;
189
+
190
+ *)
191
+ json_err "$E_VALIDATION_FAILED" "Unknown command: $cmd. Try: compare, diff, or stats."
192
+ ;;
193
+ esac