aether-colony 5.2.1 → 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 (145) hide show
  1. package/.aether/aether-utils.sh +35 -0
  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/opencode/archaeology.md +331 -0
  72. package/.aether/commands/opencode/build.md +1168 -0
  73. package/.aether/commands/opencode/chaos.md +329 -0
  74. package/.aether/commands/opencode/colonize.md +195 -0
  75. package/.aether/commands/opencode/continue.md +1436 -0
  76. package/.aether/commands/opencode/council.md +437 -0
  77. package/.aether/commands/opencode/data-clean.md +77 -0
  78. package/.aether/commands/opencode/dream.md +260 -0
  79. package/.aether/commands/opencode/entomb.md +377 -0
  80. package/.aether/commands/opencode/export-signals.md +54 -0
  81. package/.aether/commands/opencode/feedback.md +99 -0
  82. package/.aether/commands/opencode/flag.md +149 -0
  83. package/.aether/commands/opencode/flags.md +167 -0
  84. package/.aether/commands/opencode/focus.md +73 -0
  85. package/.aether/commands/opencode/help.md +157 -0
  86. package/.aether/commands/opencode/history.md +136 -0
  87. package/.aether/commands/opencode/import-signals.md +68 -0
  88. package/.aether/commands/opencode/init.md +518 -0
  89. package/.aether/commands/opencode/insert-phase.md +111 -0
  90. package/.aether/commands/opencode/interpret.md +272 -0
  91. package/.aether/commands/opencode/lay-eggs.md +213 -0
  92. package/.aether/commands/opencode/maturity.md +108 -0
  93. package/.aether/commands/opencode/memory-details.md +83 -0
  94. package/.aether/commands/opencode/migrate-state.md +165 -0
  95. package/.aether/commands/opencode/oracle.md +593 -0
  96. package/.aether/commands/opencode/organize.md +226 -0
  97. package/.aether/commands/opencode/patrol.md +626 -0
  98. package/.aether/commands/opencode/pause-colony.md +203 -0
  99. package/.aether/commands/opencode/phase.md +113 -0
  100. package/.aether/commands/opencode/pheromones.md +162 -0
  101. package/.aether/commands/opencode/plan.md +684 -0
  102. package/.aether/commands/opencode/preferences.md +71 -0
  103. package/.aether/commands/opencode/quick.md +91 -0
  104. package/.aether/commands/opencode/redirect.md +84 -0
  105. package/.aether/commands/opencode/resume-colony.md +190 -0
  106. package/.aether/commands/opencode/resume.md +394 -0
  107. package/.aether/commands/opencode/run.md +237 -0
  108. package/.aether/commands/opencode/seal.md +452 -0
  109. package/.aether/commands/opencode/skill-create.md +63 -0
  110. package/.aether/commands/opencode/status.md +307 -0
  111. package/.aether/commands/opencode/swarm.md +15 -0
  112. package/.aether/commands/opencode/tunnels.md +400 -0
  113. package/.aether/commands/opencode/update.md +127 -0
  114. package/.aether/commands/opencode/verify-castes.md +139 -0
  115. package/.aether/commands/opencode/watch.md +227 -0
  116. package/.aether/docs/command-playbooks/build-full.md +1 -1
  117. package/.aether/docs/command-playbooks/build-prep.md +10 -3
  118. package/.aether/docs/command-playbooks/build-verify.md +51 -0
  119. package/.aether/docs/command-playbooks/continue-advance.md +115 -6
  120. package/.aether/docs/command-playbooks/continue-verify.md +32 -0
  121. package/.aether/utils/clash-detect.sh +239 -0
  122. package/.aether/utils/hooks/clash-pre-tool-use.js +99 -0
  123. package/.aether/utils/merge-driver-lockfile.sh +35 -0
  124. package/.aether/utils/midden.sh +534 -0
  125. package/.aether/utils/pheromone.sh +1376 -108
  126. package/.aether/utils/queen.sh +2 -4
  127. package/.aether/utils/state-api.sh +25 -4
  128. package/.aether/utils/swarm.sh +1 -1
  129. package/.aether/utils/worktree.sh +189 -0
  130. package/.claude/commands/ant/init.md +9 -3
  131. package/.opencode/commands/ant/init.md +9 -2
  132. package/CHANGELOG.md +26 -0
  133. package/README.md +11 -8
  134. package/bin/cli.js +103 -61
  135. package/bin/lib/banner.js +14 -0
  136. package/bin/lib/init.js +8 -7
  137. package/bin/lib/interactive-setup.js +251 -0
  138. package/bin/npx-entry.js +21 -0
  139. package/bin/npx-install.js +9 -167
  140. package/bin/validate-package.sh +23 -0
  141. package/package.json +2 -2
  142. package/.aether/docs/plans/pheromone-display-plan.md +0 -257
  143. package/.aether/schemas/example-prompt-builder.xml +0 -234
  144. package/.aether/scripts/incident-test-add.sh +0 -47
  145. package/.aether/scripts/weekly-audit.sh +0 -79
@@ -1391,8 +1391,7 @@ _colony_depth() {
1391
1391
  local new_depth="${2:-}"
1392
1392
  case "$new_depth" in
1393
1393
  light|standard|deep|full)
1394
- local tmp="${DATA_DIR}/COLONY_STATE.json.tmp.$$"
1395
- jq --arg d "$new_depth" '.colony_depth = $d' "$DATA_DIR/COLONY_STATE.json" > "$tmp" && mv "$tmp" "$DATA_DIR/COLONY_STATE.json"
1394
+ NEW_DEPTH="$new_depth" _state_mutate '.colony_depth = env.NEW_DEPTH'
1396
1395
  json_ok "$(jq -n --arg depth "$new_depth" '{depth: $depth, updated: true}')"
1397
1396
  ;;
1398
1397
  *)
@@ -1476,8 +1475,7 @@ _queen_write_charter() {
1476
1475
  local current_name
1477
1476
  current_name=$(jq -r '.colony_name // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null) || true
1478
1477
  if [[ -z "$current_name" && -n "$cw_colony_name" ]]; then
1479
- local tmp_state="${DATA_DIR}/COLONY_STATE.json.tmp.$$"
1480
- jq --arg cn "$cw_colony_name" '.colony_name = $cn' "$DATA_DIR/COLONY_STATE.json" > "$tmp_state" && mv "$tmp_state" "$DATA_DIR/COLONY_STATE.json"
1478
+ CW_COLONY_NAME="$cw_colony_name" _state_mutate '.colony_name = env.CW_COLONY_NAME'
1481
1479
  fi
1482
1480
  fi
1483
1481
 
@@ -96,11 +96,32 @@ _state_write() {
96
96
 
97
97
  _state_mutate() {
98
98
  # Read-modify-write COLONY_STATE.json with a jq expression
99
- # Usage: state-mutate '<jq_expression>'
99
+ # Usage: state-mutate [--arg NAME VALUE] [--argjson NAME VALUE] '<jq_expression>'
100
+ # Supports jq --arg, --argjson, --slurpfile, --rawfile flags (forwarded to jq)
100
101
  # Acquires lock, creates backup, applies jq, validates, writes atomically
101
102
  # Returns: json_ok with mutated:true, or json_err on failure
102
103
 
103
- sm_expr="${1:-}"
104
+ # Parse jq flags (--arg, --argjson, --slurpfile, --rawfile) from arguments
105
+ # The jq expression is always the last argument (after all flags)
106
+ local sm_jq_flags=()
107
+ local sm_expr=""
108
+ local i=0
109
+ local args=("$@")
110
+
111
+ while [[ $i -lt ${#args[@]} ]]; do
112
+ case "${args[$i]}" in
113
+ --arg|--argjson|--slurpfile|--rawfile)
114
+ # Flag requires a name and value — consume next two args
115
+ sm_jq_flags+=("${args[$i]}" "${args[$((i+1))]}" "${args[$((i+2))]}")
116
+ i=$((i + 3))
117
+ ;;
118
+ *)
119
+ # Last argument is the jq expression
120
+ sm_expr="${args[$i]}"
121
+ i=$((i + 1))
122
+ ;;
123
+ esac
124
+ done
104
125
 
105
126
  if [[ -z "$sm_expr" ]]; then
106
127
  json_err "$E_VALIDATION_FAILED" "state-mutate requires a jq expression argument"
@@ -122,8 +143,8 @@ _state_mutate() {
122
143
  fi
123
144
  fi
124
145
 
125
- # Apply jq expression to current state
126
- sm_updated=$(jq "$sm_expr" "$sm_state_file" 2>/dev/null) || {
146
+ # Apply jq expression to current state (with forwarded flags)
147
+ sm_updated=$(jq ${sm_jq_flags[@]+"${sm_jq_flags[@]}"} "$sm_expr" "$sm_state_file" 2>/dev/null) || {
127
148
  release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
128
149
  json_err "$E_JSON_INVALID" "jq expression failed: $sm_expr"
129
150
  }
@@ -32,7 +32,7 @@ _autofix_checkpoint() {
32
32
  label="${1:-autofix-$(date +%s)}"
33
33
  stash_name="aether-checkpoint: $label"
34
34
  # Only stash Aether-managed directories, never touch user files
35
- if git stash push -m "$stash_name" -- $target_dirs >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: stash operation may fail
35
+ if git stash push -m "$stash_name" -- $target_dirs ":(exclude).aether/data/" >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: stash operation may fail
36
36
  json_ok "$(jq -n --arg ref "$stash_name" '{type: "stash", ref: $ref}')"
37
37
  else
38
38
  # Stash failed (possibly due to conflicts), record commit hash
@@ -0,0 +1,189 @@
1
+ #!/bin/bash
2
+ # Worktree utility functions -- extracted from aether-utils.sh
3
+ # Provides: _worktree_create, _worktree_cleanup
4
+ #
5
+ # These functions are sourced by aether-utils.sh at startup.
6
+ # All shared infrastructure (json_ok, json_err, atomic_write, acquire_lock,
7
+ # release_lock, DATA_DIR, COLONY_DATA_DIR, SCRIPT_DIR, AETHER_ROOT, error
8
+ # constants) is available.
9
+
10
+ # Default worktree location relative to AETHER_ROOT
11
+ WORKTREE_BASE_DIR="${AETHER_ROOT}/.aether/worktrees"
12
+
13
+ # _worktree_create
14
+ # Creates a git worktree for an agent working on a specific task.
15
+ #
16
+ # Usage: _worktree_create --branch <branch-name> [--base <base-branch>] [--task-id <task-id>]
17
+ # Returns JSON: {ok:true, result:{path, branch, base, worktree_dir, task_id}}
18
+ _worktree_create() {
19
+ local branch=""
20
+ local base=""
21
+ local task_id=""
22
+
23
+ # Parse arguments
24
+ while [[ $# -gt 0 ]]; do
25
+ case "$1" in
26
+ --branch) branch="${2:-}"; shift 2 ;;
27
+ --base) base="${2:-}"; shift 2 ;;
28
+ --task-id) task_id="${2:-}"; shift 2 ;;
29
+ *) shift ;;
30
+ esac
31
+ done
32
+
33
+ # Validate required arguments
34
+ if [[ -z "$branch" ]]; then
35
+ json_err "$E_VALIDATION_FAILED" "Usage: worktree-create --branch <branch-name> [--base <base-branch>] [--task-id <task-id>]"
36
+ fi
37
+
38
+ # Sanitize branch name: reject obviously dangerous patterns
39
+ if [[ "$branch" == *..* ]] || [[ "$branch" == */* ]] || [[ "$branch" == *\\* ]]; then
40
+ json_err "$E_VALIDATION_FAILED" "Branch name must not contain '..', '/', or backslashes"
41
+ fi
42
+
43
+ # Default base to current branch
44
+ if [[ -z "$base" ]]; then
45
+ base=$(git -C "$AETHER_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
46
+ fi
47
+
48
+ local worktree_dir="$WORKTREE_BASE_DIR/$branch"
49
+
50
+ # Check if worktree already exists
51
+ if [[ -d "$worktree_dir" ]]; then
52
+ json_err "$E_VALIDATION_FAILED" "Worktree already exists for branch '$branch' at $worktree_dir"
53
+ fi
54
+
55
+ # Check if branch already exists as a git branch (would indicate duplicate)
56
+ if git -C "$AETHER_ROOT" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
57
+ json_err "$E_VALIDATION_FAILED" "Branch '$branch' already exists"
58
+ fi
59
+
60
+ # Ensure base branch exists
61
+ if ! git -C "$AETHER_ROOT" show-ref --verify --quiet "refs/heads/$base" 2>/dev/null; then
62
+ json_err "$E_GIT_ERROR" "Base branch '$base' does not exist"
63
+ fi
64
+
65
+ # Ensure parent directory exists
66
+ mkdir -p "$WORKTREE_BASE_DIR"
67
+
68
+ # Create the worktree (git worktree add creates the branch automatically)
69
+ if ! git -C "$AETHER_ROOT" worktree add "$worktree_dir" -b "$branch" "$base" >/dev/null 2>&1; then
70
+ json_err "$E_GIT_ERROR" "Failed to create worktree for branch '$branch'"
71
+ fi
72
+
73
+ # Copy .aether/data/ structure to the new worktree so the agent has colony context
74
+ # Per state-contract-design.md, branch-local state lives in .aether/data/ (gitignored)
75
+ # and each worktree gets its own independent copy for colony context isolation.
76
+ if [[ -d "$AETHER_ROOT/.aether/data" ]]; then
77
+ mkdir -p "$worktree_dir/.aether/data"
78
+ cp -r "$AETHER_ROOT/.aether/data/." "$worktree_dir/.aether/data/" 2>/dev/null || true # SUPPRESS:OK -- copy: data dir may be empty
79
+ fi
80
+
81
+ # Copy exchange scripts so xml-utils.sh can source pheromone-xml.sh etc.
82
+ if [[ -d "$AETHER_ROOT/.aether/exchange" ]]; then
83
+ mkdir -p "$worktree_dir/.aether/exchange"
84
+ cp -r "$AETHER_ROOT/.aether/exchange/." "$worktree_dir/.aether/exchange/" 2>/dev/null || true
85
+ fi
86
+
87
+ # Inject main's pheromone signals into the worktree (per D-01)
88
+ # Non-blocking: if injection fails, worktree still works without injected signals
89
+ if [[ -f "$worktree_dir/.aether/data/pheromones.json" ]]; then
90
+ local main_head
91
+ main_head=$(git -C "$AETHER_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown")
92
+ (
93
+ cd "$worktree_dir" 2>/dev/null && \
94
+ DATA_DIR="$worktree_dir/.aether/data" \
95
+ COLONY_DATA_DIR="$worktree_dir/.aether/data" \
96
+ bash "$AETHER_ROOT/.aether/aether-utils.sh" \
97
+ pheromone-snapshot-inject --from-branch "$base" --from-commit "$main_head" \
98
+ >/dev/null 2>&1 || true
99
+ )
100
+ fi
101
+
102
+ # Build result JSON
103
+ local result
104
+ result=$(jq -n \
105
+ --arg path "$worktree_dir" \
106
+ --arg branch "$branch" \
107
+ --arg base "$base" \
108
+ --arg worktree_dir "$worktree_dir" \
109
+ --arg task_id "${task_id:-null}" \
110
+ '{
111
+ path: $path,
112
+ branch: $branch,
113
+ base: $base,
114
+ worktree_dir: $worktree_dir,
115
+ task_id: (if $task_id == "null" then null else $task_id end)
116
+ }')
117
+
118
+ json_ok "$result"
119
+ }
120
+
121
+ # _worktree_cleanup
122
+ # Removes a git worktree and cleans up tracking.
123
+ #
124
+ # Usage: _worktree_cleanup --branch <branch-name> [--force]
125
+ # Returns JSON: {ok:true, result:{removed, branch, path}}
126
+ _worktree_cleanup() {
127
+ local branch=""
128
+ local force=false
129
+
130
+ # Parse arguments
131
+ while [[ $# -gt 0 ]]; do
132
+ case "$1" in
133
+ --branch) branch="${2:-}"; shift 2 ;;
134
+ --force) force=true; shift ;;
135
+ *) shift ;;
136
+ esac
137
+ done
138
+
139
+ # Validate required arguments
140
+ if [[ -z "$branch" ]]; then
141
+ json_err "$E_VALIDATION_FAILED" "Usage: worktree-cleanup --branch <branch-name> [--force]"
142
+ fi
143
+
144
+ # Sanitize branch name
145
+ if [[ "$branch" == *..* ]] || [[ "$branch" == */* ]] || [[ "$branch" == *\\* ]]; then
146
+ json_err "$E_VALIDATION_FAILED" "Branch name must not contain '..', '/', or backslashes"
147
+ fi
148
+
149
+ local worktree_dir="$WORKTREE_BASE_DIR/$branch"
150
+
151
+ # Check if worktree exists
152
+ if [[ ! -d "$worktree_dir" ]]; then
153
+ json_err "$E_RESOURCE_NOT_FOUND" "No worktree found for branch '$branch'"
154
+ fi
155
+
156
+ # Check for uncommitted changes (unless --force)
157
+ # Exclude .aether/ files since they are branch-local state copies, not user changes
158
+ if [[ "$force" == "false" ]]; then
159
+ local dirty_count
160
+ dirty_count=$(git -C "$worktree_dir" status --porcelain 2>/dev/null \
161
+ | grep -v '\.aether/' \
162
+ | wc -l \
163
+ | tr -d ' ') || dirty_count=0
164
+
165
+ if [[ "$dirty_count" -gt 0 ]]; then
166
+ json_err "$E_VALIDATION_FAILED" "Worktree '$branch' has $dirty_count uncommitted changes. Use --force to remove anyway."
167
+ fi
168
+ fi
169
+
170
+ # Remove the worktree using git worktree remove
171
+ if ! git -C "$AETHER_ROOT" worktree remove "$worktree_dir" --force 2>/dev/null; then
172
+ # Fallback: manual cleanup if git worktree remove fails
173
+ rm -rf "$worktree_dir" 2>/dev/null || true
174
+ # Also prune stale worktree entries
175
+ git -C "$AETHER_ROOT" worktree prune 2>/dev/null || true
176
+ fi
177
+
178
+ # Attempt to delete the branch (best-effort -- may fail if branch is checked out elsewhere)
179
+ git -C "$AETHER_ROOT" branch -D "$branch" >/dev/null 2>&1 || true
180
+
181
+ # Build result JSON
182
+ local result
183
+ result=$(jq -n \
184
+ --arg branch "$branch" \
185
+ --arg path "$worktree_dir" \
186
+ '{removed: true, branch: $branch, path: $path}')
187
+
188
+ json_ok "$result"
189
+ }
@@ -41,7 +41,6 @@ Do not touch during init:
41
41
  - .aether/dreams/ (user notes)
42
42
  - .aether/chambers/ (archived colonies)
43
43
  - .env* files
44
- - .claude/settings.json
45
44
  - .github/workflows/
46
45
  </read_only>
47
46
 
@@ -365,7 +364,14 @@ domain_tags=$(bash .aether/aether-utils.sh domain-detect 2>/dev/null | jq -r '.r
365
364
  bash .aether/aether-utils.sh registry-add "$(pwd)" "$(jq -r '.version // "unknown"' ~/.aether/version.json 2>/dev/null || echo 'unknown')" --goal "{approved_intent}" --active true --tags "$domain_tags" 2>/dev/null || true
366
365
  cp ~/.aether/version.json .aether/version.json 2>/dev/null || true
367
366
  ```
368
- 11. Seed QUEEN.md from hive (non-blocking):
367
+ 11. Install clash detection hook and merge driver (non-blocking):
368
+ ```bash
369
+ # Install PreToolUse hook to detect file conflicts across worktrees
370
+ bash .aether/aether-utils.sh clash-setup --install 2>/dev/null || true
371
+ # Register lockfile merge driver (keeps "ours" on package-lock.json conflicts)
372
+ git config merge.lockfile.driver "bash .aether/utils/merge-driver-lockfile.sh %O %A %B" 2>/dev/null || true
373
+ ```
374
+ 12. Seed QUEEN.md from hive (non-blocking):
369
375
  ```bash
370
376
  domain_tags=$(jq -r --arg repo "$(pwd)" \
371
377
  '[.repos[] | select(.path == $repo) | .domain_tags // []] | .[0] // [] | join(",")' \
@@ -375,7 +381,7 @@ seed_args="queen-seed-from-hive --limit 5"
375
381
  seed_result=$(bash .aether/aether-utils.sh $seed_args 2>/dev/null || echo '{}')
376
382
  seeded_count=$(echo "$seed_result" | jq -r '.result.seeded // 0' 2>/dev/null || echo "0")
377
383
  ```
378
- 12. Run `bash .aether/aether-utils.sh session-init "{session_id}" "{approved_intent}"`
384
+ 13. Run `bash .aether/aether-utils.sh session-init "{session_id}" "{approved_intent}"`
379
385
 
380
386
  **Pheromone auto-apply (referenced by both re-init and fresh init paths above):**
381
387
 
@@ -377,7 +377,14 @@ domain_tags=$(bash .aether/aether-utils.sh domain-detect 2>/dev/null | jq -r '.r
377
377
  bash .aether/aether-utils.sh registry-add "$(pwd)" "$(jq -r '.version // "unknown"' ~/.aether/version.json 2>/dev/null || echo 'unknown')" --goal "{approved_intent}" --active true --tags "$domain_tags" 2>/dev/null || true
378
378
  cp ~/.aether/version.json .aether/version.json 2>/dev/null || true
379
379
  ```
380
- 11. Seed QUEEN.md from hive (non-blocking):
380
+ 11. Install clash detection hook and merge driver (non-blocking):
381
+ ```bash
382
+ # Install PreToolUse hook to detect file conflicts across worktrees
383
+ bash .aether/aether-utils.sh clash-setup --install 2>/dev/null || true
384
+ # Register lockfile merge driver (keeps "ours" on package-lock.json conflicts)
385
+ git config merge.lockfile.driver "bash .aether/utils/merge-driver-lockfile.sh %O %A %B" 2>/dev/null || true
386
+ ```
387
+ 12. Seed QUEEN.md from hive (non-blocking):
381
388
  ```bash
382
389
  domain_tags=$(jq -r --arg repo "$(pwd)" \
383
390
  '[.repos[] | select(.path == $repo) | .domain_tags // []] | .[0] // [] | join(",")' \
@@ -387,7 +394,7 @@ seed_args="queen-seed-from-hive --limit 5"
387
394
  seed_result=$(bash .aether/aether-utils.sh $seed_args 2>/dev/null || echo '{}')
388
395
  seeded_count=$(echo "$seed_result" | jq -r '.result.seeded // 0' 2>/dev/null || echo "0")
389
396
  ```
390
- 12. Run `bash .aether/aether-utils.sh session-init "{session_id}" "{approved_intent}"`
397
+ 13. Run `bash .aether/aether-utils.sh session-init "{session_id}" "{approved_intent}"`
391
398
 
392
399
  **Pheromone auto-apply (referenced by both re-init and fresh init paths above):**
393
400
 
package/CHANGELOG.md CHANGED
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [5.3.0] - 2026-03-31
11
+
12
+ Aether v2.7 — PR Workflow + Stability. Six phases (39-44) adding multi-branch safety, clash detection, and release hardening.
13
+
14
+ ### Added
15
+ - **Pheromone propagation** — Signals flow across git branches via `pheromone-snapshot-inject` and `pheromone-merge-back`; worktree creation auto-copies active pheromones
16
+ - **Midden collection** — Failure records from merged branches collected into main via `midden-collect` with idempotent dedup; cross-PR pattern detection via `midden-cross-pr-analysis`; revert-aware tagging via `midden-handle-revert`
17
+ - **Clash detection** — PreToolUse hook (`clash-pre-tool-use.js`) blocks edits to files modified in other active worktrees; `.aether/data/` files allowlisted (branch-local state)
18
+ - **Worktree utilities** — `_worktree_create` auto-copies colony context (COLONY_STATE.json, pheromones.json) and runs pheromone-snapshot-inject
19
+ - **Merge driver** — `.gitattributes` merge driver resolves package-lock.json conflicts by keeping "ours" via `merge-driver-lockfile.sh`
20
+ - **Midden wiring** — `midden-collect` and `midden-cross-pr-analysis` wired into `/ant:continue` playbooks (non-blocking, follows pheromone merge-back pattern)
21
+ - **Interactive installer** — `npx aether-colony` now shows a 3-option menu (Full setup / Global only / Repo only) with environment detection and context-sensitive defaults; supports `--global`, `--repo`, `--yes` flags for scripting
22
+ - **`aether setup` command** — CLI equivalent of `/ant:lay-eggs` for setting up Aether in a repo without Claude Code open
23
+
24
+ ### Changed
25
+ - **Package validation** — `validate-package.sh` expanded from 15 to 38+ required file entries (100% coverage of packaged utils)
26
+ - **NPX installer** — Replaced `npx-install.js` with interactive `npx-entry.js`; old installer kept as deprecation redirect
27
+ - **Package cleanliness** — 8 dev-only files excluded from npm tarball (scripts/, design docs, example schemas)
28
+ - **CLAUDE.md** — Full accuracy audit: version bumped to v2.7.0, all counts verified (5,500 lines, 35 utils, 45 commands, 509 tests)
29
+ - **README.md** — Architecture counts updated (35 utils, 45 commands, ~5,500 lines)
30
+ - **YAML commands** — 6 stale command files regenerated from YAML sources (init, plan, seal for Claude and OpenCode)
31
+
32
+ ### Fixed
33
+ - **Clash detection dispatcher** — `clash-detect.sh` and `worktree.sh` wired into `aether-utils.sh` dispatcher (source lines, dispatch cases, help JSON)
34
+ - **Init command** — Clash detection hook verification and read-only worktree list integrated into `/ant:init` Step 7.6
35
+
10
36
  ## [2.1.0] - 2026-03-24
11
37
 
12
38
  Six phases of production hardening (Phases 9-14) targeting reliability, maintainability, and developer experience.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Spawn a colony of 24 AI specialists that self-organize around your goal using ph
18
18
  *The whole is greater than the sum of its ants.*
19
19
 
20
20
  ```bash
21
- npm install -g aether-colony
21
+ npx aether-colony
22
22
  ```
23
23
 
24
24
  </div>
@@ -72,7 +72,10 @@ The colony **remembers**. Wisdom, learnings, and instincts persist across sessio
72
72
  ## 🚀 Quick Start
73
73
 
74
74
  ```bash
75
- # Install globally
75
+ # Interactive setup (recommended)
76
+ npx aether-colony
77
+
78
+ # Or install globally
76
79
  npm install -g aether-colony
77
80
 
78
81
  # In your project repo:
@@ -88,7 +91,7 @@ That's it. Five commands from zero to shipped.
88
91
  ## ✨ Key Features
89
92
 
90
93
  - 🐜 **24 Specialized Agents** — Real subagents spawned via Task tool, from builders to archaeologists
91
- - ⚡ **44 Slash Commands** — Full lifecycle management across Claude Code and OpenCode
94
+ - ⚡ **45 Slash Commands** — Full lifecycle management across Claude Code and OpenCode
92
95
  - 🎯 **Pheromone System** — Guide the colony with FOCUS, REDIRECT, FEEDBACK signals
93
96
  - 🧠 **Colony Memory** — Learnings persist across sessions via QUEEN.md wisdom
94
97
  - 🌐 **Hive Brain** — Cross-colony wisdom sharing with domain-scoped retrieval
@@ -212,10 +215,10 @@ That's it. Five commands from zero to shipped.
212
215
 
213
216
  ```
214
217
  .aether/ # 🐜 Colony files (repo-local)
215
- ├── aether-utils.sh # ⚡ Dispatcher (~5,200 lines, ~150 subcommands)
216
- ├── utils/ # 🔧 ~29 modular scripts
218
+ ├── aether-utils.sh # ⚡ Dispatcher (~5,500 lines, ~130+ subcommands)
219
+ ├── utils/ # 🔧 35 modular scripts
217
220
  ├── skills/ # 📚 28 skills (10 colony + 18 domain)
218
- ├── commands/ # 📖 44 YAML command sources
221
+ ├── commands/ # 📖 45 YAML command sources
219
222
  ├── exchange/ # 📤 XML exchange modules
220
223
  ├── docs/ # 📝 Documentation
221
224
  ├── templates/ # 📋 12 templates
@@ -229,8 +232,8 @@ That's it. Five commands from zero to shipped.
229
232
 
230
233
  ## 🔌 Works With
231
234
 
232
- - **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** — 44 slash commands + 24 agent definitions
233
- - **[OpenCode](https://github.com/opencode-ai/opencode)** — 44 slash commands + agent definitions
235
+ - **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** — 45 slash commands + 24 agent definitions
236
+ - **[OpenCode](https://github.com/opencode-ai/opencode)** — 45 slash commands + agent definitions
234
237
 
235
238
  ## ❤️ Support Aether
236
239
 
package/bin/cli.js CHANGED
@@ -1313,79 +1313,111 @@ program.on('option:quiet', () => {
1313
1313
  globalQuiet = true;
1314
1314
  });
1315
1315
 
1316
- // Install command
1317
- program
1318
- .command('install')
1319
- .description('Install commands and agents to ~/.claude/ and set up distribution hub')
1320
- .action(wrapCommand(async () => {
1321
- log(c.header(`aether-colony v${VERSION} — installing...`));
1322
-
1323
- // Sync commands to ~/.claude/commands/ant/ (with orphan cleanup)
1324
- if (!fs.existsSync(COMMANDS_SRC)) {
1325
- // Running from source repo — commands are in .claude/commands/ant/
1326
- const repoCommands = path.join(PACKAGE_DIR, '.claude', 'commands', 'ant');
1327
- if (fs.existsSync(repoCommands)) {
1328
- const result = syncDirWithCleanup(repoCommands, COMMANDS_DEST);
1329
- log(` Commands: ${result.copied} files -> ${COMMANDS_DEST}`);
1330
- if (result.removed.length > 0) {
1331
- log(` Commands: removed ${result.removed.length} stale files`);
1332
- for (const f of result.removed) log(` - ${f}`);
1333
- }
1334
- } else {
1335
- console.error(' Commands source not found. Skipping.');
1336
- }
1337
- } else {
1338
- const result = syncDirWithCleanup(COMMANDS_SRC, COMMANDS_DEST);
1316
+ /**
1317
+ * Perform a global installation of commands, agents, and hub setup.
1318
+ * Extracted so it can be called from the interactive installer.
1319
+ */
1320
+ async function performGlobalInstall() {
1321
+ log(c.header(`aether-colony v${VERSION} — installing...`));
1322
+
1323
+ // Sync commands to ~/.claude/commands/ant/ (with orphan cleanup)
1324
+ if (!fs.existsSync(COMMANDS_SRC)) {
1325
+ // Running from source repo — commands are in .claude/commands/ant/
1326
+ const repoCommands = path.join(PACKAGE_DIR, '.claude', 'commands', 'ant');
1327
+ if (fs.existsSync(repoCommands)) {
1328
+ const result = syncDirWithCleanup(repoCommands, COMMANDS_DEST);
1339
1329
  log(` Commands: ${result.copied} files -> ${COMMANDS_DEST}`);
1340
1330
  if (result.removed.length > 0) {
1341
1331
  log(` Commands: removed ${result.removed.length} stale files`);
1342
1332
  for (const f of result.removed) log(` - ${f}`);
1343
1333
  }
1334
+ } else {
1335
+ console.error(' Commands source not found. Skipping.');
1336
+ }
1337
+ } else {
1338
+ const result = syncDirWithCleanup(COMMANDS_SRC, COMMANDS_DEST);
1339
+ log(` Commands: ${result.copied} files -> ${COMMANDS_DEST}`);
1340
+ if (result.removed.length > 0) {
1341
+ log(` Commands: removed ${result.removed.length} stale files`);
1342
+ for (const f of result.removed) log(` - ${f}`);
1344
1343
  }
1344
+ }
1345
1345
 
1346
- // Sync agents to ~/.claude/agents/ant/ (with orphan cleanup)
1347
- const repoAgents = path.join(PACKAGE_DIR, '.claude', 'agents', 'ant');
1348
- if (fs.existsSync(repoAgents)) {
1349
- const result = syncDirWithCleanup(repoAgents, AGENTS_DEST);
1350
- log(` Agents (claude): ${result.copied} files -> ${AGENTS_DEST}`);
1351
- if (result.removed.length > 0) {
1352
- log(` Agents (claude): removed ${result.removed.length} stale files`);
1353
- for (const f of result.removed) log(` - ${f}`);
1354
- }
1346
+ // Sync agents to ~/.claude/agents/ant/ (with orphan cleanup)
1347
+ const repoAgents = path.join(PACKAGE_DIR, '.claude', 'agents', 'ant');
1348
+ if (fs.existsSync(repoAgents)) {
1349
+ const result = syncDirWithCleanup(repoAgents, AGENTS_DEST);
1350
+ log(` Agents (claude): ${result.copied} files -> ${AGENTS_DEST}`);
1351
+ if (result.removed.length > 0) {
1352
+ log(` Agents (claude): removed ${result.removed.length} stale files`);
1353
+ for (const f of result.removed) log(` - ${f}`);
1355
1354
  }
1355
+ }
1356
1356
 
1357
- // Sync OpenCode commands to ~/.opencode/command/ (with orphan cleanup)
1358
- const opencodeCmdsSrc = path.join(PACKAGE_DIR, '.opencode', 'commands', 'ant');
1359
- if (fs.existsSync(opencodeCmdsSrc)) {
1360
- const result = syncDirWithCleanup(opencodeCmdsSrc, OPENCODE_COMMANDS_DEST);
1361
- log(` Commands (opencode): ${result.copied} files -> ${OPENCODE_COMMANDS_DEST}`);
1362
- if (result.removed.length > 0) {
1363
- log(` Commands (opencode): removed ${result.removed.length} stale files`);
1364
- for (const f of result.removed) log(` - ${f}`);
1365
- }
1357
+ // Sync OpenCode commands to ~/.opencode/command/ (with orphan cleanup)
1358
+ const opencodeCmdsSrc = path.join(PACKAGE_DIR, '.opencode', 'commands', 'ant');
1359
+ if (fs.existsSync(opencodeCmdsSrc)) {
1360
+ const result = syncDirWithCleanup(opencodeCmdsSrc, OPENCODE_COMMANDS_DEST);
1361
+ log(` Commands (opencode): ${result.copied} files -> ${OPENCODE_COMMANDS_DEST}`);
1362
+ if (result.removed.length > 0) {
1363
+ log(` Commands (opencode): removed ${result.removed.length} stale files`);
1364
+ for (const f of result.removed) log(` - ${f}`);
1366
1365
  }
1366
+ }
1367
1367
 
1368
- // Sync OpenCode agents to ~/.opencode/agent/ (with orphan cleanup)
1369
- const opencodeAgentsSrc = path.join(PACKAGE_DIR, '.opencode', 'agents');
1370
- if (fs.existsSync(opencodeAgentsSrc)) {
1371
- const result = syncDirWithCleanup(opencodeAgentsSrc, OPENCODE_AGENTS_DEST);
1372
- log(` Agents (opencode): ${result.copied} files -> ${OPENCODE_AGENTS_DEST}`);
1373
- if (result.removed.length > 0) {
1374
- log(` Agents (opencode): removed ${result.removed.length} stale files`);
1375
- for (const f of result.removed) log(` - ${f}`);
1376
- }
1368
+ // Sync OpenCode agents to ~/.opencode/agent/ (with orphan cleanup)
1369
+ const opencodeAgentsSrc = path.join(PACKAGE_DIR, '.opencode', 'agents');
1370
+ if (fs.existsSync(opencodeAgentsSrc)) {
1371
+ const result = syncDirWithCleanup(opencodeAgentsSrc, OPENCODE_AGENTS_DEST);
1372
+ log(` Agents (opencode): ${result.copied} files -> ${OPENCODE_AGENTS_DEST}`);
1373
+ if (result.removed.length > 0) {
1374
+ log(` Agents (opencode): removed ${result.removed.length} stale files`);
1375
+ for (const f of result.removed) log(` - ${f}`);
1377
1376
  }
1377
+ }
1378
1378
 
1379
- // Set up distribution hub at ~/.aether/
1380
- log('');
1381
- log(c.colony('Setting up distribution hub...'));
1382
- setupHub();
1379
+ // Set up distribution hub at ~/.aether/
1380
+ log('');
1381
+ log(c.colony('Setting up distribution hub...'));
1382
+ setupHub();
1383
1383
 
1384
- log('');
1385
- log(c.success('Install complete.'));
1386
- log(` ${c.queen('Claude Code:')} run /ant to get started`);
1387
- log(` ${c.colony('OpenCode:')} run /ant to get started`);
1388
- log(` ${c.colony('Hub:')} ${c.dim('~/.aether/')} (for coordinated updates across repos)`);
1384
+ log('');
1385
+ log(c.success('Install complete.'));
1386
+ log(` ${c.queen('Claude Code:')} run /ant to get started`);
1387
+ log(` ${c.colony('OpenCode:')} run /ant to get started`);
1388
+ log(` ${c.colony('Hub:')} ${c.dim('~/.aether/')} (for coordinated updates across repos)`);
1389
+ }
1390
+
1391
+ // Install command
1392
+ program
1393
+ .command('install')
1394
+ .description('Install commands and agents to ~/.claude/ and set up distribution hub')
1395
+ .action(wrapCommand(performGlobalInstall));
1396
+
1397
+ // Setup command — set up Aether in the current directory from hub
1398
+ program
1399
+ .command('setup')
1400
+ .description('Set up Aether in the current directory (copies system files from hub)')
1401
+ .option('-f, --force', 'Overwrite existing setup')
1402
+ .action(wrapCommand(async (options) => {
1403
+ if (!fs.existsSync(HUB_VERSION)) {
1404
+ console.error('Aether hub not installed.');
1405
+ console.error('Run "npx aether-colony" or "aether install" to install the hub first.');
1406
+ process.exit(1);
1407
+ }
1408
+ const repoPath = process.cwd();
1409
+ log(c.header('Setting up Aether in this directory...'));
1410
+ const result = await initializeRepo(repoPath, { setupOnly: true });
1411
+ if (result.success) {
1412
+ log('');
1413
+ log(c.success('Aether is ready.'));
1414
+ log(` ${result.filesCopied} system files synced to .aether/`);
1415
+ log('');
1416
+ log(' Next steps:');
1417
+ log(' In Claude Code: /ant:init "your goal"');
1418
+ log(' Or terminal: aether init --goal "your goal"');
1419
+ log('');
1420
+ }
1389
1421
  }));
1390
1422
 
1391
1423
  // Update command
@@ -2172,10 +2204,20 @@ module.exports = {
2172
2204
  syncDirWithCleanup,
2173
2205
  syncSkillsToHub,
2174
2206
  listFilesRecursive,
2175
- cleanEmptyDirs
2207
+ cleanEmptyDirs,
2208
+ performGlobalInstall,
2209
+ run
2176
2210
  };
2177
2211
 
2212
+ /**
2213
+ * Parse CLI arguments. Called automatically when run directly,
2214
+ * or explicitly by npx-entry.js when delegating a subcommand.
2215
+ */
2216
+ function run() {
2217
+ program.parse();
2218
+ }
2219
+
2178
2220
  // Parse command line arguments only when run directly (not when required as a module)
2179
2221
  if (require.main === module) {
2180
- program.parse();
2222
+ run();
2181
2223
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared ASCII banner for Aether installers.
3
+ */
4
+
5
+ const BANNER = `
6
+ █████╗ ███████╗████████╗██╗ ██╗███████╗██████╗
7
+ ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔════╝██╔══██╗
8
+ ███████║█████╗ ██║ ███████║█████╗ ██████╔╝
9
+ ██╔══██║██╔══╝ ██║ ██╔══██║██╔══╝ ██╔══██╗
10
+ ██║ ██║███████╗ ██║ ██║ ██║███████╗██║ ██║
11
+ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
12
+ `;
13
+
14
+ module.exports = { BANNER };
package/bin/lib/init.js CHANGED
@@ -321,7 +321,7 @@ function validateInitialization(repoPath) {
321
321
  * @returns {object} Result: { success: boolean, stateFile: string|null, message: string }
322
322
  */
323
323
  async function initializeRepo(repoPath, options = {}) {
324
- const { goal, skipIfExists = false, quiet = false } = options;
324
+ const { goal, skipIfExists = false, quiet = false, setupOnly = false } = options;
325
325
 
326
326
  // Check if already initialized
327
327
  if (isInitialized(repoPath) && skipIfExists) {
@@ -411,12 +411,13 @@ locks/
411
411
  `;
412
412
  fs.writeFileSync(gitignorePath, gitignoreContent);
413
413
 
414
- // Create initial state
415
- const state = createInitialState(goal);
416
-
417
- // Write state file
418
- const stateFile = path.join(repoPath, '.aether', 'data', 'COLONY_STATE.json');
419
- fs.writeFileSync(stateFile, JSON.stringify(state, null, 2) + '\n');
414
+ // Create initial colony state (skipped in setupOnly mode)
415
+ let stateFile = null;
416
+ if (!setupOnly) {
417
+ const state = createInitialState(goal);
418
+ stateFile = path.join(repoPath, '.aether', 'data', 'COLONY_STATE.json');
419
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2) + '\n');
420
+ }
420
421
 
421
422
  // Get hub version
422
423
  const hubVersion = readJsonSafe(HUB_VERSION);