aether-colony 5.3.2 → 5.4.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 (281) hide show
  1. package/.aether/aether-utils.sh +181 -5
  2. package/.aether/commands/archaeology.yaml +3 -3
  3. package/.aether/commands/build.yaml +80 -45
  4. package/.aether/commands/chaos.yaml +7 -7
  5. package/.aether/commands/colonize.yaml +17 -17
  6. package/.aether/commands/continue.yaml +40 -40
  7. package/.aether/commands/council.yaml +6 -6
  8. package/.aether/commands/data-clean.yaml +3 -3
  9. package/.aether/commands/dream.yaml +2 -2
  10. package/.aether/commands/entomb.yaml +12 -12
  11. package/.aether/commands/export-signals.yaml +2 -2
  12. package/.aether/commands/feedback.yaml +6 -6
  13. package/.aether/commands/flag.yaml +2 -2
  14. package/.aether/commands/flags.yaml +4 -4
  15. package/.aether/commands/focus.yaml +6 -6
  16. package/.aether/commands/help.yaml +1 -1
  17. package/.aether/commands/history.yaml +1 -1
  18. package/.aether/commands/import-signals.yaml +2 -2
  19. package/.aether/commands/init.yaml +44 -27
  20. package/.aether/commands/insert-phase.yaml +1 -1
  21. package/.aether/commands/interpret.yaml +2 -2
  22. package/.aether/commands/lay-eggs.yaml +3 -3
  23. package/.aether/commands/maturity.yaml +2 -2
  24. package/.aether/commands/memory-details.yaml +1 -1
  25. package/.aether/commands/migrate-state.yaml +1 -1
  26. package/.aether/commands/oracle.yaml +147 -82
  27. package/.aether/commands/organize.yaml +5 -5
  28. package/.aether/commands/patrol.yaml +8 -8
  29. package/.aether/commands/pause-colony.yaml +7 -7
  30. package/.aether/commands/phase.yaml +1 -1
  31. package/.aether/commands/pheromones.yaml +1 -1
  32. package/.aether/commands/plan.yaml +14 -14
  33. package/.aether/commands/quick.yaml +4 -4
  34. package/.aether/commands/redirect.yaml +6 -6
  35. package/.aether/commands/resume-colony.yaml +9 -9
  36. package/.aether/commands/resume.yaml +5 -38
  37. package/.aether/commands/run.yaml +10 -10
  38. package/.aether/commands/seal.yaml +33 -33
  39. package/.aether/commands/skill-create.yaml +4 -4
  40. package/.aether/commands/status.yaml +14 -14
  41. package/.aether/commands/swarm.yaml +14 -14
  42. package/.aether/commands/tunnels.yaml +7 -7
  43. package/.aether/commands/update.yaml +1 -1
  44. package/.aether/commands/verify-castes.yaml +3 -3
  45. package/.aether/commands/watch.yaml +15 -15
  46. package/.aether/docs/command-playbooks/build-complete.md +48 -15
  47. package/.aether/docs/command-playbooks/build-context.md +11 -11
  48. package/.aether/docs/command-playbooks/build-full.md +76 -76
  49. package/.aether/docs/command-playbooks/build-prep.md +10 -10
  50. package/.aether/docs/command-playbooks/build-verify.md +27 -27
  51. package/.aether/docs/command-playbooks/build-wave.md +38 -38
  52. package/.aether/docs/command-playbooks/continue-advance.md +60 -27
  53. package/.aether/docs/command-playbooks/continue-finalize.md +25 -11
  54. package/.aether/docs/command-playbooks/continue-full.md +60 -46
  55. package/.aether/docs/command-playbooks/continue-gates.md +18 -18
  56. package/.aether/docs/command-playbooks/continue-verify.md +10 -10
  57. package/.aether/docs/source-of-truth-map.md +10 -10
  58. package/.aether/docs/structural-learning-stack.md +283 -0
  59. package/.aether/templates/colony-state-template.json +1 -0
  60. package/.aether/utils/consolidation-seal.sh +196 -0
  61. package/.aether/utils/consolidation.sh +127 -0
  62. package/.aether/utils/curation-ants/archivist.sh +97 -0
  63. package/.aether/utils/curation-ants/critic.sh +214 -0
  64. package/.aether/utils/curation-ants/herald.sh +102 -0
  65. package/.aether/utils/curation-ants/janitor.sh +121 -0
  66. package/.aether/utils/curation-ants/librarian.sh +99 -0
  67. package/.aether/utils/curation-ants/nurse.sh +153 -0
  68. package/.aether/utils/curation-ants/orchestrator.sh +181 -0
  69. package/.aether/utils/curation-ants/scribe.sh +164 -0
  70. package/.aether/utils/curation-ants/sentinel.sh +119 -0
  71. package/.aether/utils/event-bus.sh +301 -0
  72. package/.aether/utils/graph.sh +559 -0
  73. package/.aether/utils/instinct-store.sh +401 -0
  74. package/.aether/utils/learning.sh +79 -7
  75. package/.aether/utils/oracle/oracle-stop-hook.sh +896 -0
  76. package/.aether/utils/session.sh +13 -0
  77. package/.aether/utils/state-api.sh +1 -1
  78. package/.aether/utils/trust-scoring.sh +347 -0
  79. package/.aether/utils/worktree.sh +97 -0
  80. package/.claude/commands/ant/archaeology.md +2 -2
  81. package/.claude/commands/ant/chaos.md +4 -4
  82. package/.claude/commands/ant/colonize.md +9 -9
  83. package/.claude/commands/ant/council.md +6 -6
  84. package/.claude/commands/ant/data-clean.md +3 -3
  85. package/.claude/commands/ant/dream.md +2 -2
  86. package/.claude/commands/ant/entomb.md +9 -9
  87. package/.claude/commands/ant/export-signals.md +2 -2
  88. package/.claude/commands/ant/feedback.md +4 -4
  89. package/.claude/commands/ant/flag.md +2 -2
  90. package/.claude/commands/ant/flags.md +4 -4
  91. package/.claude/commands/ant/focus.md +4 -4
  92. package/.claude/commands/ant/help.md +1 -1
  93. package/.claude/commands/ant/history.md +1 -1
  94. package/.claude/commands/ant/import-signals.md +2 -2
  95. package/.claude/commands/ant/init.md +44 -27
  96. package/.claude/commands/ant/insert-phase.md +1 -1
  97. package/.claude/commands/ant/interpret.md +2 -2
  98. package/.claude/commands/ant/lay-eggs.md +2 -2
  99. package/.claude/commands/ant/maturity.md +2 -2
  100. package/.claude/commands/ant/memory-details.md +1 -1
  101. package/.claude/commands/ant/migrate-state.md +1 -1
  102. package/.claude/commands/ant/oracle.md +78 -42
  103. package/.claude/commands/ant/organize.md +3 -3
  104. package/.claude/commands/ant/patrol.md +8 -8
  105. package/.claude/commands/ant/pause-colony.md +5 -5
  106. package/.claude/commands/ant/phase.md +1 -1
  107. package/.claude/commands/ant/pheromones.md +1 -1
  108. package/.claude/commands/ant/plan.md +8 -8
  109. package/.claude/commands/ant/quick.md +4 -4
  110. package/.claude/commands/ant/redirect.md +4 -4
  111. package/.claude/commands/ant/resume-colony.md +5 -5
  112. package/.claude/commands/ant/resume.md +17 -29
  113. package/.claude/commands/ant/run.md +10 -10
  114. package/.claude/commands/ant/seal.md +25 -25
  115. package/.claude/commands/ant/skill-create.md +2 -2
  116. package/.claude/commands/ant/status.md +14 -14
  117. package/.claude/commands/ant/swarm.md +14 -14
  118. package/.claude/commands/ant/tunnels.md +4 -4
  119. package/.claude/commands/ant/update.md +1 -1
  120. package/.claude/commands/ant/verify-castes.md +2 -2
  121. package/.claude/commands/ant/watch.md +8 -8
  122. package/.opencode/commands/ant/archaeology.md +1 -1
  123. package/.opencode/commands/ant/build.md +80 -45
  124. package/.opencode/commands/ant/chaos.md +3 -3
  125. package/.opencode/commands/ant/colonize.md +8 -8
  126. package/.opencode/commands/ant/continue.md +40 -40
  127. package/.opencode/commands/ant/council.md +5 -5
  128. package/.opencode/commands/ant/data-clean.md +2 -2
  129. package/.opencode/commands/ant/dream.md +1 -1
  130. package/.opencode/commands/ant/entomb.md +3 -3
  131. package/.opencode/commands/ant/export-signals.md +1 -1
  132. package/.opencode/commands/ant/feedback.md +2 -2
  133. package/.opencode/commands/ant/flag.md +1 -1
  134. package/.opencode/commands/ant/flags.md +3 -3
  135. package/.opencode/commands/ant/focus.md +2 -2
  136. package/.opencode/commands/ant/import-signals.md +1 -1
  137. package/.opencode/commands/ant/init.md +44 -27
  138. package/.opencode/commands/ant/insert-phase.md +1 -1
  139. package/.opencode/commands/ant/interpret.md +1 -1
  140. package/.opencode/commands/ant/lay-eggs.md +2 -2
  141. package/.opencode/commands/ant/maturity.md +1 -1
  142. package/.opencode/commands/ant/memory-details.md +1 -1
  143. package/.opencode/commands/ant/oracle.md +69 -40
  144. package/.opencode/commands/ant/organize.md +2 -2
  145. package/.opencode/commands/ant/patrol.md +8 -8
  146. package/.opencode/commands/ant/pause-colony.md +2 -2
  147. package/.opencode/commands/ant/pheromones.md +1 -1
  148. package/.opencode/commands/ant/plan.md +6 -6
  149. package/.opencode/commands/ant/quick.md +4 -4
  150. package/.opencode/commands/ant/redirect.md +2 -2
  151. package/.opencode/commands/ant/resume-colony.md +4 -4
  152. package/.opencode/commands/ant/resume.md +5 -17
  153. package/.opencode/commands/ant/run.md +10 -10
  154. package/.opencode/commands/ant/seal.md +8 -8
  155. package/.opencode/commands/ant/skill-create.md +2 -2
  156. package/.opencode/commands/ant/status.md +10 -10
  157. package/.opencode/commands/ant/tunnels.md +3 -3
  158. package/.opencode/commands/ant/verify-castes.md +1 -1
  159. package/.opencode/commands/ant/watch.md +7 -7
  160. package/CHANGELOG.md +83 -0
  161. package/README.md +22 -9
  162. package/bin/cli.js +118 -3
  163. package/bin/lib/binary-downloader.js +267 -0
  164. package/bin/lib/update-transaction.js +27 -3
  165. package/bin/lib/version-gate.js +179 -0
  166. package/bin/npx-entry.js +0 -0
  167. package/package.json +1 -1
  168. package/.aether/agents/aether-ambassador.md +0 -140
  169. package/.aether/agents/aether-archaeologist.md +0 -108
  170. package/.aether/agents/aether-architect.md +0 -133
  171. package/.aether/agents/aether-auditor.md +0 -144
  172. package/.aether/agents/aether-builder.md +0 -184
  173. package/.aether/agents/aether-chaos.md +0 -115
  174. package/.aether/agents/aether-chronicler.md +0 -122
  175. package/.aether/agents/aether-gatekeeper.md +0 -116
  176. package/.aether/agents/aether-includer.md +0 -117
  177. package/.aether/agents/aether-keeper.md +0 -177
  178. package/.aether/agents/aether-measurer.md +0 -128
  179. package/.aether/agents/aether-oracle.md +0 -137
  180. package/.aether/agents/aether-probe.md +0 -133
  181. package/.aether/agents/aether-queen.md +0 -286
  182. package/.aether/agents/aether-route-setter.md +0 -130
  183. package/.aether/agents/aether-sage.md +0 -106
  184. package/.aether/agents/aether-scout.md +0 -101
  185. package/.aether/agents/aether-surveyor-disciplines.md +0 -391
  186. package/.aether/agents/aether-surveyor-nest.md +0 -329
  187. package/.aether/agents/aether-surveyor-pathogens.md +0 -264
  188. package/.aether/agents/aether-surveyor-provisions.md +0 -334
  189. package/.aether/agents/aether-tracker.md +0 -137
  190. package/.aether/agents/aether-watcher.md +0 -174
  191. package/.aether/agents/aether-weaver.md +0 -130
  192. package/.aether/commands/claude/archaeology.md +0 -334
  193. package/.aether/commands/claude/build.md +0 -65
  194. package/.aether/commands/claude/chaos.md +0 -336
  195. package/.aether/commands/claude/colonize.md +0 -259
  196. package/.aether/commands/claude/continue.md +0 -60
  197. package/.aether/commands/claude/council.md +0 -507
  198. package/.aether/commands/claude/data-clean.md +0 -81
  199. package/.aether/commands/claude/dream.md +0 -268
  200. package/.aether/commands/claude/entomb.md +0 -498
  201. package/.aether/commands/claude/export-signals.md +0 -57
  202. package/.aether/commands/claude/feedback.md +0 -96
  203. package/.aether/commands/claude/flag.md +0 -151
  204. package/.aether/commands/claude/flags.md +0 -169
  205. package/.aether/commands/claude/focus.md +0 -76
  206. package/.aether/commands/claude/help.md +0 -154
  207. package/.aether/commands/claude/history.md +0 -140
  208. package/.aether/commands/claude/import-signals.md +0 -71
  209. package/.aether/commands/claude/init.md +0 -505
  210. package/.aether/commands/claude/insert-phase.md +0 -105
  211. package/.aether/commands/claude/interpret.md +0 -278
  212. package/.aether/commands/claude/lay-eggs.md +0 -210
  213. package/.aether/commands/claude/maturity.md +0 -113
  214. package/.aether/commands/claude/memory-details.md +0 -77
  215. package/.aether/commands/claude/migrate-state.md +0 -171
  216. package/.aether/commands/claude/oracle.md +0 -642
  217. package/.aether/commands/claude/organize.md +0 -232
  218. package/.aether/commands/claude/patrol.md +0 -620
  219. package/.aether/commands/claude/pause-colony.md +0 -233
  220. package/.aether/commands/claude/phase.md +0 -115
  221. package/.aether/commands/claude/pheromones.md +0 -156
  222. package/.aether/commands/claude/plan.md +0 -693
  223. package/.aether/commands/claude/preferences.md +0 -65
  224. package/.aether/commands/claude/quick.md +0 -100
  225. package/.aether/commands/claude/redirect.md +0 -76
  226. package/.aether/commands/claude/resume-colony.md +0 -197
  227. package/.aether/commands/claude/resume.md +0 -388
  228. package/.aether/commands/claude/run.md +0 -231
  229. package/.aether/commands/claude/seal.md +0 -774
  230. package/.aether/commands/claude/skill-create.md +0 -286
  231. package/.aether/commands/claude/status.md +0 -410
  232. package/.aether/commands/claude/swarm.md +0 -349
  233. package/.aether/commands/claude/tunnels.md +0 -426
  234. package/.aether/commands/claude/update.md +0 -132
  235. package/.aether/commands/claude/verify-castes.md +0 -143
  236. package/.aether/commands/claude/watch.md +0 -239
  237. package/.aether/commands/opencode/archaeology.md +0 -331
  238. package/.aether/commands/opencode/build.md +0 -1168
  239. package/.aether/commands/opencode/chaos.md +0 -329
  240. package/.aether/commands/opencode/colonize.md +0 -195
  241. package/.aether/commands/opencode/continue.md +0 -1436
  242. package/.aether/commands/opencode/council.md +0 -437
  243. package/.aether/commands/opencode/data-clean.md +0 -77
  244. package/.aether/commands/opencode/dream.md +0 -260
  245. package/.aether/commands/opencode/entomb.md +0 -377
  246. package/.aether/commands/opencode/export-signals.md +0 -54
  247. package/.aether/commands/opencode/feedback.md +0 -99
  248. package/.aether/commands/opencode/flag.md +0 -149
  249. package/.aether/commands/opencode/flags.md +0 -167
  250. package/.aether/commands/opencode/focus.md +0 -73
  251. package/.aether/commands/opencode/help.md +0 -157
  252. package/.aether/commands/opencode/history.md +0 -136
  253. package/.aether/commands/opencode/import-signals.md +0 -68
  254. package/.aether/commands/opencode/init.md +0 -518
  255. package/.aether/commands/opencode/insert-phase.md +0 -111
  256. package/.aether/commands/opencode/interpret.md +0 -272
  257. package/.aether/commands/opencode/lay-eggs.md +0 -213
  258. package/.aether/commands/opencode/maturity.md +0 -108
  259. package/.aether/commands/opencode/memory-details.md +0 -83
  260. package/.aether/commands/opencode/migrate-state.md +0 -165
  261. package/.aether/commands/opencode/oracle.md +0 -593
  262. package/.aether/commands/opencode/organize.md +0 -226
  263. package/.aether/commands/opencode/patrol.md +0 -626
  264. package/.aether/commands/opencode/pause-colony.md +0 -203
  265. package/.aether/commands/opencode/phase.md +0 -113
  266. package/.aether/commands/opencode/pheromones.md +0 -162
  267. package/.aether/commands/opencode/plan.md +0 -684
  268. package/.aether/commands/opencode/preferences.md +0 -71
  269. package/.aether/commands/opencode/quick.md +0 -91
  270. package/.aether/commands/opencode/redirect.md +0 -84
  271. package/.aether/commands/opencode/resume-colony.md +0 -190
  272. package/.aether/commands/opencode/resume.md +0 -394
  273. package/.aether/commands/opencode/run.md +0 -237
  274. package/.aether/commands/opencode/seal.md +0 -452
  275. package/.aether/commands/opencode/skill-create.md +0 -63
  276. package/.aether/commands/opencode/status.md +0 -307
  277. package/.aether/commands/opencode/swarm.md +0 -15
  278. package/.aether/commands/opencode/tunnels.md +0 -400
  279. package/.aether/commands/opencode/update.md +0 -127
  280. package/.aether/commands/opencode/verify-castes.md +0 -139
  281. package/.aether/commands/opencode/watch.md +0 -227
package/bin/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const crypto = require('crypto');
6
- const { execSync } = require('child_process');
6
+ const { execSync, spawnSync } = require('child_process');
7
7
  const { program } = require('commander');
8
8
 
9
9
  // Error handling imports
@@ -1204,6 +1204,71 @@ function setupHub() {
1204
1204
  }
1205
1205
  }
1206
1206
 
1207
+ /**
1208
+ * Refresh the Go binary during update flow.
1209
+ * Downloads if missing or outdated. Non-blocking — failures are logged, never thrown.
1210
+ *
1211
+ * @param {string} version - Target version string
1212
+ * @param {object} [options] - Options
1213
+ * @param {boolean} [options.quiet=false] - Suppress output
1214
+ * @returns {Promise<{refreshed: boolean, reason?: string}>} Result
1215
+ */
1216
+ async function refreshBinary(version, options = {}) {
1217
+ const { quiet = false } = options;
1218
+
1219
+ // Binary refresh is non-blocking — update always completes
1220
+ try {
1221
+ const binaryPath = path.join(HOME, '.aether', 'bin',
1222
+ process.platform === 'win32' ? 'aether.exe' : 'aether');
1223
+
1224
+ // Check if binary exists
1225
+ if (!fs.existsSync(binaryPath)) {
1226
+ if (!quiet) log(` ${c.colony('Binary:')} missing, downloading v${version}...`);
1227
+ const { downloadBinary } = require('./lib/binary-downloader');
1228
+ const result = await downloadBinary(version);
1229
+ if (result.success) {
1230
+ if (!quiet) log(` ${c.success('Binary:')} installed v${version} -> ${c.dim(result.path)}`);
1231
+ return { refreshed: true };
1232
+ } else {
1233
+ if (!quiet) log(` ${c.warning('Binary:')} download skipped (${result.reason})`);
1234
+ return { refreshed: false, reason: result.reason };
1235
+ }
1236
+ }
1237
+
1238
+ // Binary exists — check version
1239
+ let installedVersion = null;
1240
+ try {
1241
+ const { execFileSync } = require('child_process');
1242
+ const output = execFileSync(binaryPath, ['version'], { encoding: 'utf8', timeout: 5000 });
1243
+ // Parse version from output like "aether v5.3.3" or just "v5.3.3"
1244
+ const match = output.match(/v?(\d+\.\d+\.\d+)/);
1245
+ if (match) installedVersion = match[1];
1246
+ } catch {
1247
+ // Can't determine version — download fresh
1248
+ }
1249
+
1250
+ if (!installedVersion || installedVersion !== version) {
1251
+ if (!quiet) log(` ${c.colony('Binary:')} updating ${installedVersion || 'unknown'} -> v${version}...`);
1252
+ const { downloadBinary } = require('./lib/binary-downloader');
1253
+ const result = await downloadBinary(version);
1254
+ if (result.success) {
1255
+ if (!quiet) log(` ${c.success('Binary:')} updated to v${version}`);
1256
+ return { refreshed: true };
1257
+ } else {
1258
+ if (!quiet) log(` ${c.warning('Binary:')} update skipped (${result.reason})`);
1259
+ return { refreshed: false, reason: result.reason };
1260
+ }
1261
+ }
1262
+
1263
+ // Binary is up to date
1264
+ if (!quiet) log(` ${c.dim(`Binary: v${version} (up to date)`)}`);
1265
+ return { refreshed: false, reason: 'up to date' };
1266
+ } catch (err) {
1267
+ if (!quiet) log(` ${c.warning('Binary:')} refresh failed (${err.message})`);
1268
+ return { refreshed: false, reason: err.message };
1269
+ }
1270
+ }
1271
+
1207
1272
  async function updateRepo(repoPath, sourceVersion, opts) {
1208
1273
  opts = opts || {};
1209
1274
  const dryRun = opts.dryRun || false;
@@ -1276,6 +1341,8 @@ async function updateRepo(repoPath, sourceVersion, opts) {
1276
1341
  removed: systemRemoved + commandsRemoved + agentsRemoved + rulesRemoved + agentsClaudeRemoved,
1277
1342
  removedFiles: allRemovedFiles,
1278
1343
  stashCreated: !!transaction.checkpoint?.stashRef,
1344
+ stashRestored: result.stash_restored || false,
1345
+ stashConflict: result.stash_conflict || false,
1279
1346
  checkpoint_id: result.checkpoint_id,
1280
1347
  cleanup: cleanupResult,
1281
1348
  };
@@ -1381,6 +1448,19 @@ async function performGlobalInstall() {
1381
1448
  log(c.colony('Setting up distribution hub...'));
1382
1449
  setupHub();
1383
1450
 
1451
+ // Download Go binary (non-blocking)
1452
+ try {
1453
+ const { downloadBinary } = require('./lib/binary-downloader');
1454
+ const result = await downloadBinary(VERSION);
1455
+ if (result.success) {
1456
+ log(` ${c.colony('Binary:')} ${c.dim(result.path)}`);
1457
+ } else {
1458
+ log(` ${c.warning('Binary:')} skipped (${result.reason})`);
1459
+ }
1460
+ } catch (err) {
1461
+ log(` ${c.warning('Binary:')} download failed (${err.message})`);
1462
+ }
1463
+
1384
1464
  log('');
1385
1465
  log(c.success('Install complete.'));
1386
1466
  log(` ${c.queen('Claude Code:')} run /ant to get started`);
@@ -1535,7 +1615,13 @@ program
1535
1615
  log(` Distribution chain: ${c.success('\u2713')} clean`);
1536
1616
  }
1537
1617
  if (result.stashCreated) {
1538
- log(` Stash created. Recover with: cd ${repo.path} && git stash pop`);
1618
+ if (result.stashRestored) {
1619
+ log(` Local changes restored automatically`);
1620
+ } else if (result.stashConflict) {
1621
+ log(` ${c.warning('Stash restore had conflicts')} — resolve manually: cd ${repo.path} && git stash pop`);
1622
+ } else {
1623
+ log(` Stash created. Recover with: cd ${repo.path} && git stash pop`);
1624
+ }
1539
1625
  }
1540
1626
  updated++;
1541
1627
  } else {
@@ -1556,6 +1642,11 @@ program
1556
1642
  writeJsonSync(HUB_REGISTRY, registry);
1557
1643
  }
1558
1644
 
1645
+ // Binary refresh is non-blocking — update always completes
1646
+ if (!dryRun) {
1647
+ await refreshBinary(sourceVersion, { quiet: false });
1648
+ }
1649
+
1559
1650
  const label = dryRun ? 'would update' : 'updated';
1560
1651
  let summary = `\nSummary: ${updated} ${label}, ${upToDate} up to date, ${pruned} pruned`;
1561
1652
  if (dirty > 0) summary += `, ${dirty} dirty (skipped)`;
@@ -1639,12 +1730,22 @@ program
1639
1730
  if (result.cleanup && result.cleanup.cleaned.length === 0 && result.cleanup.failed.length === 0) {
1640
1731
  console.log(` Distribution chain: ${c.success('\u2713')} clean`);
1641
1732
  }
1642
- if (result.stashCreated) {
1733
+ if (result.stashRestored) {
1734
+ console.log(` ${c.success('\u2713')} Local changes restored`);
1735
+ } else if (result.stashConflict) {
1736
+ console.log(` ${c.warning('\u26A0')} Stash restore had conflicts — resolve manually: git stash pop`);
1737
+ } else if (result.stashCreated) {
1643
1738
  console.log(' Git stash created. Recover with: git stash pop');
1644
1739
  }
1645
1740
  if (result.checkpoint_id) {
1646
1741
  console.log(` Checkpoint: ${result.checkpoint_id}`);
1647
1742
  }
1743
+
1744
+ // Binary refresh is non-blocking — update always completes
1745
+ if (!dryRun) {
1746
+ await refreshBinary(sourceVersion);
1747
+ }
1748
+
1648
1749
  console.log(' Colony data (.aether/data/) untouched.');
1649
1750
  } catch (error) {
1650
1751
  // Handle UpdateError with prominent recovery commands (UPDATE-04)
@@ -2201,6 +2302,7 @@ module.exports = {
2201
2302
  loadCheckpointMetadata,
2202
2303
  saveCheckpointMetadata,
2203
2304
  isUserData,
2305
+ refreshBinary,
2204
2306
  syncDirWithCleanup,
2205
2307
  syncSkillsToHub,
2206
2308
  listFilesRecursive,
@@ -2219,5 +2321,18 @@ function run() {
2219
2321
 
2220
2322
  // Parse command line arguments only when run directly (not when required as a module)
2221
2323
  if (require.main === module) {
2324
+ // Delegation shim: if Go binary is available and version matches, pass through to it.
2325
+ // Commands that must run in Node.js (install, update, setup) are excluded.
2326
+ const { shouldDelegate, getBinaryPath } = require('./lib/version-gate');
2327
+ if (shouldDelegate(process.argv)) {
2328
+ const { spawnSync } = require('child_process');
2329
+ const binaryPath = getBinaryPath();
2330
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
2331
+ stdio: 'inherit',
2332
+ env: process.env,
2333
+ });
2334
+ process.exit(result.status);
2335
+ }
2336
+
2222
2337
  run();
2223
2338
  }
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Binary Downloader for Aether Colony
4
+ *
5
+ * Downloads the correct platform Go binary from GitHub Releases during
6
+ * `npm install -g aether-colony`. Verifies SHA-256 checksum before
7
+ * atomic install to ~/.aether/bin/aether.
8
+ *
9
+ * Uses ONLY Node.js built-in modules. No external dependencies.
10
+ */
11
+
12
+ const https = require('https');
13
+ const http = require('http');
14
+ const crypto = require('crypto');
15
+ const fs = require('fs');
16
+ const fsPromises = require('fs/promises');
17
+ const os = require('os');
18
+ const path = require('path');
19
+ const { execFile } = require('child_process');
20
+ const { pipeline } = require('stream/promises');
21
+ const { promisify } = require('util');
22
+
23
+ const execFileAsync = promisify(execFile);
24
+
25
+ // Platform detection maps process.platform + process.arch to goreleaser naming
26
+ const PLATFORM_MAP = {
27
+ darwin: 'darwin',
28
+ linux: 'linux',
29
+ win32: 'windows',
30
+ };
31
+
32
+ const ARCH_MAP = {
33
+ x64: 'amd64',
34
+ arm64: 'arm64',
35
+ };
36
+
37
+ /**
38
+ * Detect platform and architecture, mapped to goreleaser naming.
39
+ * @returns {{os: string, arch: string}|null} Platform info or null if unsupported
40
+ */
41
+ function getPlatformArch() {
42
+ const goos = PLATFORM_MAP[process.platform];
43
+ const goarch = ARCH_MAP[process.arch];
44
+ if (!goos || !goarch) return null;
45
+ return { os: goos, arch: goarch };
46
+ }
47
+
48
+ /**
49
+ * Download a URL following HTTP redirects (GitHub releases always 302).
50
+ * Node.js https.get() does NOT follow redirects automatically.
51
+ *
52
+ * @param {string} url - URL to download
53
+ * @param {number} maxRedirects - Maximum redirect hops (default 5)
54
+ * @returns {Promise<import('http').IncomingMessage>} Response stream on 200
55
+ */
56
+ function downloadWithRedirects(url, maxRedirects = 5) {
57
+ return new Promise((resolve, reject) => {
58
+ function attempt(currentUrl, redirectsLeft) {
59
+ const client = currentUrl.startsWith('https') ? https : http;
60
+ client.get(currentUrl, (res) => {
61
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
62
+ if (redirectsLeft <= 0) {
63
+ res.resume();
64
+ return reject(new Error('Too many redirects'));
65
+ }
66
+ res.resume(); // Drain response body before next request
67
+ return attempt(res.headers.location, redirectsLeft - 1);
68
+ }
69
+ if (res.statusCode !== 200) {
70
+ res.resume();
71
+ return reject(new Error(`HTTP ${res.statusCode}`));
72
+ }
73
+ resolve(res);
74
+ }).on('error', reject);
75
+ }
76
+ attempt(url, maxRedirects);
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Download a URL and return the full response body as a string.
82
+ *
83
+ * @param {string} url - URL to download
84
+ * @returns {Promise<string>} Response body text
85
+ */
86
+ async function downloadText(url) {
87
+ const response = await downloadWithRedirects(url);
88
+ return new Promise((resolve, reject) => {
89
+ let data = '';
90
+ response.on('data', (chunk) => { data += chunk; });
91
+ response.on('end', () => resolve(data));
92
+ response.on('error', reject);
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Parse goreleaser checksums.txt to find the hash for a specific filename.
98
+ * Format: <sha256_hex> <filename> (two-space separator)
99
+ *
100
+ * @param {string} checksumsContent - Full checksums.txt content
101
+ * @param {string} filename - Archive filename to find
102
+ * @returns {string|null} SHA-256 hex digest or null if not found
103
+ */
104
+ function findChecksum(checksumsContent, filename) {
105
+ const lines = checksumsContent.split('\n');
106
+ for (const line of lines) {
107
+ const parts = line.split(' ');
108
+ if (parts.length >= 2 && parts[1] === filename) {
109
+ return parts[0];
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+
115
+ /**
116
+ * Download an archive to a temp file while computing SHA-256 hash.
117
+ * Hashing happens during the stream -- no extra I/O pass.
118
+ *
119
+ * @param {string} url - Archive URL
120
+ * @param {string} tmpPath - Temp file path for the download
121
+ * @returns {Promise<{hash: string, tmpPath: string}>} Computed hash and temp file path
122
+ */
123
+ async function downloadAndHash(url, tmpPath) {
124
+ const hash = crypto.createHash('sha256');
125
+ const fileStream = fs.createWriteStream(tmpPath);
126
+
127
+ const response = await downloadWithRedirects(url);
128
+
129
+ // Hash while streaming -- zero extra I/O
130
+ response.on('data', (chunk) => hash.update(chunk));
131
+
132
+ await pipeline(response, fileStream);
133
+
134
+ return { hash: hash.digest('hex'), tmpPath };
135
+ }
136
+
137
+ /**
138
+ * Extract binary from archive using system tar command.
139
+ * tar is available on macOS, Linux, and Windows 10+.
140
+ *
141
+ * @param {string} archivePath - Path to the archive file
142
+ * @param {string} destDir - Directory to extract into
143
+ * @param {string} goos - Goreleaser OS name (darwin, linux, windows)
144
+ * @returns {Promise<string>} Path to the extracted binary
145
+ */
146
+ async function extractBinary(archivePath, destDir, goos) {
147
+ const binaryName = goos === 'windows' ? 'aether.exe' : 'aether';
148
+
149
+ await fsPromises.mkdir(destDir, { recursive: true });
150
+
151
+ await execFileAsync('tar', [
152
+ '-xf', archivePath,
153
+ '-C', destDir,
154
+ '--strip-components', '1',
155
+ binaryName,
156
+ ]);
157
+
158
+ return path.join(destDir, binaryName);
159
+ }
160
+
161
+ /**
162
+ * Atomically install a binary by renaming from temp to final path.
163
+ * fs.rename() is atomic on both POSIX and Windows (Node 14+).
164
+ *
165
+ * @param {string} extractedBinary - Path to the extracted binary
166
+ * @param {string} targetPath - Final install path
167
+ */
168
+ async function atomicInstall(extractedBinary, targetPath) {
169
+ await fsPromises.mkdir(path.dirname(targetPath), { recursive: true });
170
+ await fsPromises.rename(extractedBinary, targetPath);
171
+ // Set executable permission on Unix
172
+ if (process.platform !== 'win32') {
173
+ await fsPromises.chmod(targetPath, 0o755);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Download and install the correct platform Go binary from GitHub Releases.
179
+ *
180
+ * This function NEVER throws. On any error it returns {success: false, reason: string}.
181
+ *
182
+ * @param {string} version - Version string (e.g. "5.3.3")
183
+ * @param {object} [options] - Download options
184
+ * @param {boolean} [options.quiet=false] - Suppress progress output
185
+ * @param {number} [options.timeout=30000] - Download timeout in milliseconds
186
+ * @returns {Promise<{success: boolean, reason?: string, path?: string}>} Result
187
+ */
188
+ async function downloadBinary(version, options = {}) {
189
+ const { quiet = false, timeout = 30000 } = options;
190
+
191
+ try {
192
+ // 1. Platform detection
193
+ const platform = getPlatformArch();
194
+ if (!platform) {
195
+ return { success: false, reason: `Unsupported platform: ${process.platform}/${process.arch}` };
196
+ }
197
+
198
+ // 2. Construct URLs
199
+ const archiveExt = platform.os === 'windows' ? '.zip' : '.tar.gz';
200
+ const archiveFilename = `aether_${version}_${platform.os}_${platform.arch}${archiveExt}`;
201
+ const baseUrl = `https://github.com/calcosmic/Aether/releases/download/v${version}`;
202
+ const archiveUrl = `${baseUrl}/${archiveFilename}`;
203
+ const checksumsUrl = `${baseUrl}/checksums.txt`;
204
+
205
+ // 3. Download checksums.txt
206
+ let checksumsContent;
207
+ try {
208
+ checksumsContent = await downloadText(checksumsUrl);
209
+ } catch (err) {
210
+ return { success: false, reason: `Failed to download checksums: ${err.message}` };
211
+ }
212
+
213
+ // 4. Parse expected hash
214
+ const expectedHash = findChecksum(checksumsContent, archiveFilename);
215
+
216
+ // 5. Download archive and compute hash
217
+ const tmpArchive = path.join(os.tmpdir(), `aether-download-${Date.now()}.tmp`);
218
+
219
+ // Race download against timeout, clearing timer on success
220
+ let timeoutId;
221
+ const downloadPromise = downloadAndHash(archiveUrl, tmpArchive);
222
+ const timeoutPromise = new Promise((_, reject) => {
223
+ timeoutId = setTimeout(() => reject(new Error('Download timed out')), timeout);
224
+ });
225
+ const downloadResult = await Promise.race([downloadPromise, timeoutPromise])
226
+ .finally(() => clearTimeout(timeoutId));
227
+
228
+ const { hash: actualHash } = downloadResult;
229
+
230
+ // 6. Verify checksum
231
+ if (expectedHash && actualHash !== expectedHash) {
232
+ await fsPromises.unlink(tmpArchive).catch(() => {});
233
+ return { success: false, reason: `Checksum mismatch for ${archiveFilename}` };
234
+ }
235
+
236
+ // 7. Extract binary from archive
237
+ const tmpBinaryDir = path.join(os.tmpdir(), `aether-extract-${Date.now()}`);
238
+ const extractedBinary = await extractBinary(tmpArchive, tmpBinaryDir, platform.os);
239
+
240
+ // 8. Atomic install
241
+ const targetPath = path.join(
242
+ os.homedir(), '.aether', 'bin',
243
+ process.platform === 'win32' ? 'aether.exe' : 'aether'
244
+ );
245
+ await atomicInstall(extractedBinary, targetPath);
246
+
247
+ // 9. Cleanup temp files (best effort)
248
+ await fsPromises.unlink(tmpArchive).catch(() => {});
249
+ await fsPromises.rm(tmpBinaryDir, { recursive: true }).catch(() => {});
250
+
251
+ return { success: true, path: targetPath };
252
+ } catch (err) {
253
+ return { success: false, reason: err.message };
254
+ }
255
+ }
256
+
257
+ module.exports = {
258
+ downloadBinary,
259
+ getPlatformArch,
260
+ // Internal helpers exported for testing (prefixed with _)
261
+ _findChecksum: findChecksum,
262
+ _downloadWithRedirects: downloadWithRedirects,
263
+ _downloadText: downloadText,
264
+ _downloadAndHash: downloadAndHash,
265
+ _extractBinary: extractBinary,
266
+ _atomicInstall: atomicInstall,
267
+ };
@@ -172,8 +172,9 @@ class UpdateTransaction {
172
172
 
173
173
  // Directories to exclude from sync (user data, local state, and separately-synced dirs)
174
174
  // v4.0: archive and chambers added — these are private and must not sync to target repos
175
- // v6.0: oracle, midden, exchange added for complete user data protection
176
- this.EXCLUDE_DIRS = ['data', 'dreams', 'oracle', 'midden', 'checkpoints', 'locks', 'temp', 'agents', 'commands', 'rules', 'archive', 'chambers', 'exchange'];
175
+ // v6.0: oracle, midden added for complete user data protection
176
+ // v7.0: exchange removed from exclusion .sh scripts must distribute; .xml/.json data excluded by file extension
177
+ this.EXCLUDE_DIRS = ['data', 'dreams', 'oracle', 'midden', 'checkpoints', 'locks', 'temp', 'agents', 'commands', 'rules', 'archive', 'chambers'];
177
178
 
178
179
  // Files to exclude from sync (user wisdom, protected files)
179
180
  this.EXCLUDE_FILES = ['QUEEN.md'];
@@ -192,7 +193,6 @@ class UpdateTransaction {
192
193
  'temp',
193
194
  'archive',
194
195
  'chambers',
195
- 'exchange',
196
196
  ]);
197
197
  this.managedPrefixes = [
198
198
  '.claude/commands/ant',
@@ -792,6 +792,11 @@ class UpdateTransaction {
792
792
  if (this.EXCLUDE_FILES.includes(basename)) {
793
793
  return true;
794
794
  }
795
+ // Protect exchange data files — only .sh scripts should distribute
796
+ // Only filter files (not directories), so the exchange dir itself is traversed
797
+ if (parts.includes('exchange') && !basename.endsWith('.sh') && !relPath.endsWith('exchange')) {
798
+ return true;
799
+ }
795
800
  return false;
796
801
  }
797
802
 
@@ -1664,6 +1669,23 @@ class UpdateTransaction {
1664
1669
  (this.syncResult?.agents?.removed?.length || 0) +
1665
1670
  (this.syncResult?.rules?.removed?.length || 0);
1666
1671
 
1672
+ // Restore stashed files on success
1673
+ let stashRestored = false;
1674
+ let stashConflict = false;
1675
+ if (!dryRun && this.checkpoint?.stashRef) {
1676
+ try {
1677
+ execSync(`git stash pop ${this.checkpoint.stashRef}`, {
1678
+ cwd: this.repoPath,
1679
+ stdio: 'pipe',
1680
+ });
1681
+ stashRestored = true;
1682
+ this.log(` Restored stash ${this.checkpoint.stashRef}`);
1683
+ } catch (err) {
1684
+ stashConflict = true;
1685
+ this.log(` Warning: stash pop had conflicts — manual merge needed`);
1686
+ }
1687
+ }
1688
+
1667
1689
  return {
1668
1690
  success: true,
1669
1691
  status: dryRun ? 'dry-run' : 'updated',
@@ -1672,6 +1694,8 @@ class UpdateTransaction {
1672
1694
  files_removed: filesRemoved,
1673
1695
  sync_result: this.syncResult,
1674
1696
  cleanup_result: this.cleanupResult || { cleaned: [], failed: [] },
1697
+ stash_restored: stashRestored,
1698
+ stash_conflict: stashConflict,
1675
1699
  };
1676
1700
 
1677
1701
  } catch (error) {
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Version Gate Module
5
+ *
6
+ * Checks whether the Go binary at ~/.aether/bin/aether exists, is executable,
7
+ * and reports a version matching the npm package version. Provides delegation
8
+ * logic so that `aether` commands route to the Go binary when the gate passes
9
+ * and fall back to the Node.js CLI when it does not.
10
+ *
11
+ * Requirements: GATE-01, GATE-02, SHM-01, SHM-02
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { execSync } = require('child_process');
17
+
18
+ const HOME = process.env.HOME || process.env.USERPROFILE;
19
+ const BINARY_PATH = HOME ? path.join(HOME, '.aether', 'bin', 'aether') : null;
20
+
21
+ // Commands that must always run in Node.js regardless of binary availability (SHM-02)
22
+ const NODE_ONLY_COMMANDS = ['install', 'update', 'setup', 'setup-hub'];
23
+
24
+ /**
25
+ * Compare two semver version strings.
26
+ * Handles optional 'v' prefix and compares major.minor.patch numerically.
27
+ * Pre-release tags are ignored for comparison purposes (treated as the base version).
28
+ *
29
+ * @param {string} a - First version string
30
+ * @param {string} b - Second version string
31
+ * @returns {number} -1 if a < b, 0 if a === b, 1 if a > b
32
+ */
33
+ function compareVersions(a, b) {
34
+ const stripPrefix = (v) => String(v).replace(/^v/, '');
35
+ const parseParts = (v) => {
36
+ const cleaned = stripPrefix(v);
37
+ // Take only the numeric parts (ignore pre-release tags like -alpha.1)
38
+ const numericPart = cleaned.split('-')[0];
39
+ return numericPart.split('.').map((n) => {
40
+ const parsed = parseInt(n, 10);
41
+ return isNaN(parsed) ? 0 : parsed;
42
+ });
43
+ };
44
+
45
+ const aParts = parseParts(a);
46
+ const bParts = parseParts(b);
47
+ const maxLen = Math.max(aParts.length, bParts.length);
48
+
49
+ for (let i = 0; i < maxLen; i++) {
50
+ const aVal = aParts[i] || 0;
51
+ const bVal = bParts[i] || 0;
52
+ if (aVal < bVal) return -1;
53
+ if (aVal > bVal) return 1;
54
+ }
55
+
56
+ return 0;
57
+ }
58
+
59
+ /**
60
+ * Get the expected path to the Go binary.
61
+ * @returns {string|null} Absolute path to the binary, or null if HOME is unset
62
+ */
63
+ function getBinaryPath() {
64
+ return BINARY_PATH;
65
+ }
66
+
67
+ /**
68
+ * Check whether the Go binary is available and its version matches the npm package.
69
+ *
70
+ * Returns an object describing the binary state:
71
+ * - available: boolean - true if binary exists, is executable, and version matches
72
+ * - path: string - the expected binary path
73
+ * - version: string|null - the binary's reported version (null if unavailable)
74
+ * - reason: string|null - why the gate failed, if it did
75
+ *
76
+ * @param {object} [opts] - Options
77
+ * @param {string} [opts.binaryPath] - Override binary path (for testing)
78
+ * @param {string} [opts.packageVersion] - Override package version (for testing)
79
+ * @param {object} [opts.fs] - Override fs module (for testing)
80
+ * @param {object} [opts.childProcess] - Override child_process (for testing)
81
+ * @returns {{ available: boolean, path: string, version: string|null, reason: string|null }}
82
+ */
83
+ function checkBinary(opts) {
84
+ opts = opts || {};
85
+ const binaryPath = opts.binaryPath || BINARY_PATH;
86
+ const pkgVersion = opts.packageVersion || require('../../package.json').version;
87
+ const fsMod = opts.fs || fs;
88
+ const cp = opts.childProcess || { execSync };
89
+
90
+ if (!binaryPath) {
91
+ return { available: false, path: binaryPath, version: null, reason: 'HOME not set' };
92
+ }
93
+
94
+ // Check binary exists
95
+ if (!fsMod.existsSync(binaryPath)) {
96
+ return { available: false, path: binaryPath, version: null, reason: 'binary not found' };
97
+ }
98
+
99
+ // Check executable
100
+ try {
101
+ fsMod.accessSync(binaryPath, fsMod.constants.X_OK);
102
+ } catch {
103
+ return { available: false, path: binaryPath, version: null, reason: 'binary not executable' };
104
+ }
105
+
106
+ // Get binary version — call `aether version` and strip "aether v" prefix
107
+ let binaryVersion;
108
+ try {
109
+ const output = cp.execSync(`"${binaryPath}" version`, {
110
+ encoding: 'utf8',
111
+ timeout: 5000,
112
+ stdio: ['pipe', 'pipe', 'pipe'],
113
+ }).trim();
114
+ // Strip "aether " or "aether v" prefix (e.g. "aether v5.3.3" → "5.3.3")
115
+ binaryVersion = output.replace(/^aether\s+v?/, '');
116
+ } catch {
117
+ return { available: false, path: binaryPath, version: null, reason: 'binary version check failed' };
118
+ }
119
+
120
+ // Compare versions (compareVersions handles v-prefix on both sides)
121
+ if (compareVersions(binaryVersion, pkgVersion) !== 0) {
122
+ return {
123
+ available: false,
124
+ path: binaryPath,
125
+ version: binaryVersion,
126
+ reason: `version mismatch: binary=${binaryVersion}, package=${pkgVersion}`,
127
+ };
128
+ }
129
+
130
+ return { available: true, path: binaryPath, version: binaryVersion, reason: null };
131
+ }
132
+
133
+ /**
134
+ * Determine whether the current command should be delegated to the Go binary.
135
+ *
136
+ * @param {string[]} argv - process.argv (or equivalent)
137
+ * @param {object} [opts] - Options (forwarded to checkBinary)
138
+ * @returns {boolean} True if the command should be delegated to Go
139
+ */
140
+ function shouldDelegate(argv, opts) {
141
+ opts = opts || {};
142
+
143
+ // Extract command name: skip node and script path
144
+ // argv[0] = node, argv[1] = script path, argv[2] = command name
145
+ const args = argv.slice(2);
146
+ const command = args[0];
147
+
148
+ // No command means help/version — still delegate if possible
149
+ if (!command) {
150
+ const check = checkBinary(opts);
151
+ return check.available;
152
+ }
153
+
154
+ // Strip leading dashes
155
+ const cleanCommand = command.replace(/^--?/, '');
156
+
157
+ // Node-only commands never delegate (SHM-02)
158
+ if (NODE_ONLY_COMMANDS.includes(cleanCommand)) {
159
+ return false;
160
+ }
161
+
162
+ // Global flags (--help, --version, --no-color, --quiet) — delegate if binary available
163
+ if (command.startsWith('-')) {
164
+ const check = checkBinary(opts);
165
+ return check.available;
166
+ }
167
+
168
+ // All other commands: delegate if version gate passes
169
+ const check = checkBinary(opts);
170
+ return check.available;
171
+ }
172
+
173
+ module.exports = {
174
+ compareVersions,
175
+ checkBinary,
176
+ shouldDelegate,
177
+ getBinaryPath,
178
+ NODE_ONLY_COMMANDS,
179
+ };
package/bin/npx-entry.js CHANGED
File without changes