aether-colony 5.0.0 → 5.2.1

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 (317) hide show
  1. package/.aether/aether-utils.sh +3226 -3345
  2. package/.aether/agents-claude/aether-ambassador.md +265 -0
  3. package/.aether/agents-claude/aether-archaeologist.md +327 -0
  4. package/.aether/agents-claude/aether-architect.md +236 -0
  5. package/.aether/agents-claude/aether-auditor.md +271 -0
  6. package/.aether/agents-claude/aether-builder.md +224 -0
  7. package/.aether/agents-claude/aether-chaos.md +269 -0
  8. package/.aether/agents-claude/aether-chronicler.md +305 -0
  9. package/.aether/agents-claude/aether-gatekeeper.md +330 -0
  10. package/.aether/agents-claude/aether-includer.md +374 -0
  11. package/.aether/agents-claude/aether-keeper.md +272 -0
  12. package/.aether/agents-claude/aether-measurer.md +322 -0
  13. package/.aether/agents-claude/aether-oracle.md +237 -0
  14. package/.aether/agents-claude/aether-probe.md +211 -0
  15. package/.aether/agents-claude/aether-queen.md +330 -0
  16. package/.aether/agents-claude/aether-route-setter.md +178 -0
  17. package/.aether/agents-claude/aether-sage.md +418 -0
  18. package/.aether/agents-claude/aether-scout.md +179 -0
  19. package/.aether/agents-claude/aether-surveyor-disciplines.md +417 -0
  20. package/.aether/agents-claude/aether-surveyor-nest.md +355 -0
  21. package/.aether/agents-claude/aether-surveyor-pathogens.md +289 -0
  22. package/.aether/agents-claude/aether-surveyor-provisions.md +360 -0
  23. package/.aether/agents-claude/aether-tracker.md +270 -0
  24. package/.aether/agents-claude/aether-watcher.md +280 -0
  25. package/.aether/agents-claude/aether-weaver.md +248 -0
  26. package/.aether/commands/archaeology.yaml +653 -0
  27. package/.aether/commands/build.yaml +1221 -0
  28. package/.aether/commands/chaos.yaml +653 -0
  29. package/.aether/commands/colonize.yaml +442 -0
  30. package/.aether/commands/continue.yaml +1484 -0
  31. package/.aether/commands/council.yaml +509 -0
  32. package/.aether/commands/data-clean.yaml +80 -0
  33. package/.aether/commands/dream.yaml +275 -0
  34. package/.aether/commands/entomb.yaml +863 -0
  35. package/.aether/commands/export-signals.yaml +64 -0
  36. package/.aether/commands/feedback.yaml +158 -0
  37. package/.aether/commands/flag.yaml +160 -0
  38. package/.aether/commands/flags.yaml +177 -0
  39. package/.aether/commands/focus.yaml +112 -0
  40. package/.aether/commands/help.yaml +167 -0
  41. package/.aether/commands/history.yaml +137 -0
  42. package/.aether/commands/import-signals.yaml +79 -0
  43. package/.aether/commands/init.yaml +502 -0
  44. package/.aether/commands/insert-phase.yaml +102 -0
  45. package/.aether/commands/interpret.yaml +285 -0
  46. package/.aether/commands/lay-eggs.yaml +224 -0
  47. package/.aether/commands/maturity.yaml +122 -0
  48. package/.aether/commands/memory-details.yaml +74 -0
  49. package/.aether/commands/migrate-state.yaml +174 -0
  50. package/.aether/commands/oracle.yaml +1224 -0
  51. package/.aether/commands/organize.yaml +446 -0
  52. package/.aether/commands/patrol.yaml +621 -0
  53. package/.aether/commands/pause-colony.yaml +424 -0
  54. package/.aether/commands/phase.yaml +124 -0
  55. package/.aether/commands/pheromones.yaml +153 -0
  56. package/.aether/commands/plan.yaml +1364 -0
  57. package/.aether/commands/preferences.yaml +63 -0
  58. package/.aether/commands/quick.yaml +104 -0
  59. package/.aether/commands/redirect.yaml +123 -0
  60. package/.aether/commands/resume-colony.yaml +375 -0
  61. package/.aether/commands/resume.yaml +407 -0
  62. package/.aether/commands/run.yaml +229 -0
  63. package/.aether/commands/seal.yaml +1214 -0
  64. package/.aether/commands/skill-create.yaml +337 -0
  65. package/.aether/commands/status.yaml +408 -0
  66. package/.aether/commands/swarm.yaml +352 -0
  67. package/.aether/commands/tunnels.yaml +814 -0
  68. package/.aether/commands/update.yaml +131 -0
  69. package/.aether/commands/verify-castes.yaml +159 -0
  70. package/.aether/commands/watch.yaml +454 -0
  71. package/.aether/docs/INCIDENT_TEMPLATE.md +32 -0
  72. package/.aether/docs/QUEEN-SYSTEM.md +11 -11
  73. package/.aether/docs/README.md +32 -2
  74. package/.aether/docs/command-playbooks/README.md +23 -0
  75. package/.aether/docs/command-playbooks/build-complete.md +349 -0
  76. package/.aether/docs/command-playbooks/build-context.md +282 -0
  77. package/.aether/docs/command-playbooks/build-full.md +1683 -0
  78. package/.aether/docs/command-playbooks/build-prep.md +284 -0
  79. package/.aether/docs/command-playbooks/build-verify.md +405 -0
  80. package/.aether/docs/command-playbooks/build-wave.md +749 -0
  81. package/.aether/docs/command-playbooks/continue-advance.md +524 -0
  82. package/.aether/docs/command-playbooks/continue-finalize.md +447 -0
  83. package/.aether/docs/command-playbooks/continue-full.md +1725 -0
  84. package/.aether/docs/command-playbooks/continue-gates.md +686 -0
  85. package/.aether/docs/command-playbooks/continue-verify.md +407 -0
  86. package/.aether/docs/context-continuity.md +84 -0
  87. package/.aether/docs/disciplines/DISCIPLINES.md +9 -7
  88. package/.aether/docs/error-codes.md +1 -1
  89. package/.aether/docs/known-issues.md +34 -173
  90. package/.aether/docs/pheromones.md +86 -6
  91. package/.aether/docs/plans/pheromone-display-plan.md +257 -0
  92. package/.aether/docs/queen-commands.md +10 -9
  93. package/.aether/docs/source-of-truth-map.md +132 -0
  94. package/.aether/docs/xml-utilities.md +47 -0
  95. package/.aether/rules/aether-colony.md +23 -13
  96. package/.aether/scripts/incident-test-add.sh +47 -0
  97. package/.aether/scripts/weekly-audit.sh +79 -0
  98. package/.aether/skills/.index.json +649 -0
  99. package/.aether/skills/colony/.manifest.json +16 -0
  100. package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
  101. package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
  102. package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
  103. package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
  104. package/.aether/skills/colony/context-management/SKILL.md +80 -0
  105. package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
  106. package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
  107. package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
  108. package/.aether/skills/colony/state-safety/SKILL.md +84 -0
  109. package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
  110. package/.aether/skills/domain/.manifest.json +24 -0
  111. package/.aether/skills/domain/README.md +33 -0
  112. package/.aether/skills/domain/django/SKILL.md +49 -0
  113. package/.aether/skills/domain/docker/SKILL.md +52 -0
  114. package/.aether/skills/domain/golang/SKILL.md +52 -0
  115. package/.aether/skills/domain/graphql/SKILL.md +51 -0
  116. package/.aether/skills/domain/html-css/SKILL.md +48 -0
  117. package/.aether/skills/domain/nextjs/SKILL.md +45 -0
  118. package/.aether/skills/domain/nodejs/SKILL.md +53 -0
  119. package/.aether/skills/domain/postgresql/SKILL.md +53 -0
  120. package/.aether/skills/domain/prisma/SKILL.md +59 -0
  121. package/.aether/skills/domain/python/SKILL.md +50 -0
  122. package/.aether/skills/domain/rails/SKILL.md +52 -0
  123. package/.aether/skills/domain/react/SKILL.md +45 -0
  124. package/.aether/skills/domain/rest-api/SKILL.md +58 -0
  125. package/.aether/skills/domain/svelte/SKILL.md +47 -0
  126. package/.aether/skills/domain/tailwind/SKILL.md +45 -0
  127. package/.aether/skills/domain/testing/SKILL.md +53 -0
  128. package/.aether/skills/domain/typescript/SKILL.md +58 -0
  129. package/.aether/skills/domain/vue/SKILL.md +49 -0
  130. package/.aether/templates/QUEEN.md.template +23 -41
  131. package/.aether/templates/colony-state-reset.jq.template +1 -0
  132. package/.aether/templates/colony-state.template.json +4 -0
  133. package/.aether/templates/learning-observations.template.json +6 -0
  134. package/.aether/templates/midden.template.json +13 -0
  135. package/.aether/templates/pheromones.template.json +6 -0
  136. package/.aether/templates/session.template.json +9 -0
  137. package/.aether/utils/atomic-write.sh +63 -17
  138. package/.aether/utils/chamber-utils.sh +145 -2
  139. package/.aether/utils/council.sh +425 -0
  140. package/.aether/utils/emoji-audit.sh +166 -0
  141. package/.aether/utils/error-handler.sh +21 -7
  142. package/.aether/utils/file-lock.sh +182 -27
  143. package/.aether/utils/flag.sh +278 -0
  144. package/.aether/utils/hive.sh +572 -0
  145. package/.aether/utils/immune.sh +508 -0
  146. package/.aether/utils/learning.sh +1928 -0
  147. package/.aether/utils/midden.sh +520 -0
  148. package/.aether/utils/oracle/oracle.md +168 -0
  149. package/.aether/utils/oracle/oracle.sh +1023 -0
  150. package/.aether/utils/pheromone.sh +2029 -0
  151. package/.aether/utils/queen.sh +1710 -0
  152. package/.aether/utils/scan.sh +860 -0
  153. package/.aether/utils/semantic-cli.sh +10 -8
  154. package/.aether/utils/session.sh +816 -0
  155. package/.aether/utils/skills.sh +509 -0
  156. package/.aether/utils/spawn-tree.sh +103 -271
  157. package/.aether/utils/spawn.sh +260 -0
  158. package/.aether/utils/state-api.sh +389 -0
  159. package/.aether/utils/state-loader.sh +8 -6
  160. package/.aether/utils/suggest.sh +611 -0
  161. package/.aether/utils/swarm-display.sh +10 -1
  162. package/.aether/utils/swarm.sh +1004 -0
  163. package/.aether/utils/watch-spawn-tree.sh +11 -2
  164. package/.aether/utils/xml-compose.sh +2 -2
  165. package/.aether/utils/xml-convert.sh +9 -5
  166. package/.aether/utils/xml-core.sh +5 -9
  167. package/.aether/utils/xml-query.sh +4 -4
  168. package/.aether/workers.md +86 -67
  169. package/.claude/agents/ant/aether-ambassador.md +2 -1
  170. package/.claude/agents/ant/aether-archaeologist.md +6 -1
  171. package/.claude/agents/ant/aether-architect.md +236 -0
  172. package/.claude/agents/ant/aether-auditor.md +6 -1
  173. package/.claude/agents/ant/aether-builder.md +38 -1
  174. package/.claude/agents/ant/aether-chaos.md +2 -1
  175. package/.claude/agents/ant/aether-chronicler.md +1 -0
  176. package/.claude/agents/ant/aether-gatekeeper.md +6 -1
  177. package/.claude/agents/ant/aether-includer.md +1 -0
  178. package/.claude/agents/ant/aether-keeper.md +1 -0
  179. package/.claude/agents/ant/aether-measurer.md +6 -1
  180. package/.claude/agents/ant/aether-oracle.md +237 -0
  181. package/.claude/agents/ant/aether-probe.md +2 -1
  182. package/.claude/agents/ant/aether-queen.md +6 -1
  183. package/.claude/agents/ant/aether-route-setter.md +6 -1
  184. package/.claude/agents/ant/aether-sage.md +68 -3
  185. package/.claude/agents/ant/aether-scout.md +38 -1
  186. package/.claude/agents/ant/aether-surveyor-disciplines.md +2 -1
  187. package/.claude/agents/ant/aether-surveyor-nest.md +2 -1
  188. package/.claude/agents/ant/aether-surveyor-pathogens.md +2 -1
  189. package/.claude/agents/ant/aether-surveyor-provisions.md +2 -1
  190. package/.claude/agents/ant/aether-tracker.md +6 -1
  191. package/.claude/agents/ant/aether-watcher.md +37 -1
  192. package/.claude/agents/ant/aether-weaver.md +2 -1
  193. package/.claude/commands/ant/archaeology.md +1 -8
  194. package/.claude/commands/ant/build.md +43 -1159
  195. package/.claude/commands/ant/chaos.md +1 -14
  196. package/.claude/commands/ant/colonize.md +3 -14
  197. package/.claude/commands/ant/continue.md +40 -1026
  198. package/.claude/commands/ant/council.md +213 -15
  199. package/.claude/commands/ant/data-clean.md +81 -0
  200. package/.claude/commands/ant/dream.md +12 -9
  201. package/.claude/commands/ant/entomb.md +62 -87
  202. package/.claude/commands/ant/export-signals.md +57 -0
  203. package/.claude/commands/ant/feedback.md +18 -0
  204. package/.claude/commands/ant/flag.md +12 -0
  205. package/.claude/commands/ant/flags.md +22 -8
  206. package/.claude/commands/ant/focus.md +18 -0
  207. package/.claude/commands/ant/help.md +40 -8
  208. package/.claude/commands/ant/history.md +3 -0
  209. package/.claude/commands/ant/import-signals.md +71 -0
  210. package/.claude/commands/ant/init.md +349 -191
  211. package/.claude/commands/ant/insert-phase.md +105 -0
  212. package/.claude/commands/ant/interpret.md +11 -0
  213. package/.claude/commands/ant/lay-eggs.md +167 -158
  214. package/.claude/commands/ant/maturity.md +22 -11
  215. package/.claude/commands/ant/memory-details.md +77 -0
  216. package/.claude/commands/ant/migrate-state.md +6 -0
  217. package/.claude/commands/ant/oracle.md +317 -62
  218. package/.claude/commands/ant/organize.md +10 -5
  219. package/.claude/commands/ant/patrol.md +620 -0
  220. package/.claude/commands/ant/pause-colony.md +8 -22
  221. package/.claude/commands/ant/phase.md +26 -37
  222. package/.claude/commands/ant/pheromones.md +156 -0
  223. package/.claude/commands/ant/plan.md +199 -50
  224. package/.claude/commands/ant/preferences.md +65 -0
  225. package/.claude/commands/ant/quick.md +100 -0
  226. package/.claude/commands/ant/redirect.md +18 -0
  227. package/.claude/commands/ant/resume-colony.md +37 -22
  228. package/.claude/commands/ant/resume.md +60 -7
  229. package/.claude/commands/ant/run.md +231 -0
  230. package/.claude/commands/ant/seal.md +506 -78
  231. package/.claude/commands/ant/skill-create.md +286 -0
  232. package/.claude/commands/ant/status.md +171 -1
  233. package/.claude/commands/ant/swarm.md +11 -23
  234. package/.claude/commands/ant/tunnels.md +1 -0
  235. package/.claude/commands/ant/update.md +58 -135
  236. package/.claude/commands/ant/verify-castes.md +90 -42
  237. package/.claude/commands/ant/watch.md +1 -0
  238. package/.opencode/agents/aether-ambassador.md +1 -1
  239. package/.opencode/agents/aether-architect.md +133 -0
  240. package/.opencode/agents/aether-builder.md +3 -3
  241. package/.opencode/agents/aether-oracle.md +137 -0
  242. package/.opencode/agents/aether-queen.md +1 -1
  243. package/.opencode/agents/aether-route-setter.md +1 -1
  244. package/.opencode/agents/aether-scout.md +1 -1
  245. package/.opencode/agents/aether-surveyor-disciplines.md +6 -1
  246. package/.opencode/agents/aether-surveyor-nest.md +6 -1
  247. package/.opencode/agents/aether-surveyor-pathogens.md +6 -1
  248. package/.opencode/agents/aether-surveyor-provisions.md +6 -1
  249. package/.opencode/agents/aether-tracker.md +1 -1
  250. package/.opencode/agents/aether-watcher.md +1 -1
  251. package/.opencode/agents/aether-weaver.md +1 -1
  252. package/.opencode/commands/ant/archaeology.md +7 -14
  253. package/.opencode/commands/ant/build.md +54 -88
  254. package/.opencode/commands/ant/chaos.md +7 -24
  255. package/.opencode/commands/ant/colonize.md +10 -17
  256. package/.opencode/commands/ant/continue.md +595 -66
  257. package/.opencode/commands/ant/council.md +150 -18
  258. package/.opencode/commands/ant/data-clean.md +77 -0
  259. package/.opencode/commands/ant/dream.md +15 -17
  260. package/.opencode/commands/ant/entomb.md +28 -18
  261. package/.opencode/commands/ant/export-signals.md +54 -0
  262. package/.opencode/commands/ant/feedback.md +24 -5
  263. package/.opencode/commands/ant/flag.md +16 -4
  264. package/.opencode/commands/ant/flags.md +24 -10
  265. package/.opencode/commands/ant/focus.md +22 -5
  266. package/.opencode/commands/ant/help.md +41 -8
  267. package/.opencode/commands/ant/history.md +9 -0
  268. package/.opencode/commands/ant/import-signals.md +68 -0
  269. package/.opencode/commands/ant/init.md +396 -154
  270. package/.opencode/commands/ant/insert-phase.md +111 -0
  271. package/.opencode/commands/ant/interpret.md +16 -0
  272. package/.opencode/commands/ant/lay-eggs.md +184 -112
  273. package/.opencode/commands/ant/maturity.md +18 -2
  274. package/.opencode/commands/ant/memory-details.md +83 -0
  275. package/.opencode/commands/ant/migrate-state.md +12 -0
  276. package/.opencode/commands/ant/oracle.md +322 -67
  277. package/.opencode/commands/ant/organize.md +14 -12
  278. package/.opencode/commands/ant/patrol.md +626 -0
  279. package/.opencode/commands/ant/pause-colony.md +12 -29
  280. package/.opencode/commands/ant/phase.md +30 -40
  281. package/.opencode/commands/ant/pheromones.md +162 -0
  282. package/.opencode/commands/ant/plan.md +210 -57
  283. package/.opencode/commands/ant/preferences.md +71 -0
  284. package/.opencode/commands/ant/quick.md +91 -0
  285. package/.opencode/commands/ant/redirect.md +22 -5
  286. package/.opencode/commands/ant/resume-colony.md +41 -29
  287. package/.opencode/commands/ant/resume.md +80 -20
  288. package/.opencode/commands/ant/run.md +237 -0
  289. package/.opencode/commands/ant/seal.md +230 -25
  290. package/.opencode/commands/ant/skill-create.md +63 -0
  291. package/.opencode/commands/ant/status.md +125 -30
  292. package/.opencode/commands/ant/swarm.md +3 -345
  293. package/.opencode/commands/ant/tunnels.md +3 -9
  294. package/.opencode/commands/ant/update.md +63 -127
  295. package/.opencode/commands/ant/verify-castes.md +96 -42
  296. package/.opencode/commands/ant/watch.md +7 -0
  297. package/CHANGELOG.md +368 -1
  298. package/README.md +195 -324
  299. package/bin/cli.js +236 -429
  300. package/bin/generate-commands.js +186 -0
  301. package/bin/generate-commands.sh +128 -89
  302. package/bin/lib/spawn-logger.js +0 -15
  303. package/bin/lib/update-transaction.js +285 -35
  304. package/bin/npx-install.js +178 -0
  305. package/bin/validate-package.sh +85 -3
  306. package/package.json +16 -4
  307. package/.aether/CONTEXT.md +0 -160
  308. package/.aether/docs/QUEEN.md +0 -84
  309. package/.aether/exchange/colony-registry.xml +0 -11
  310. package/.aether/exchange/pheromones.xml +0 -87
  311. package/.aether/exchange/queen-wisdom.xml +0 -14
  312. package/.aether/model-profiles.yaml +0 -100
  313. package/.aether/utils/spawn-with-model.sh +0 -56
  314. package/bin/lib/model-profiles.js +0 -445
  315. package/bin/lib/model-verify.js +0 -288
  316. package/bin/lib/proxy-health.js +0 -253
  317. package/bin/lib/telemetry.js +0 -441
@@ -172,10 +172,35 @@ 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
- this.EXCLUDE_DIRS = ['data', 'dreams', 'checkpoints', 'locks', 'temp', 'agents', 'commands', 'rules', 'archive', 'chambers'];
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'];
177
+
178
+ // Files to exclude from sync (user wisdom, protected files)
179
+ this.EXCLUDE_FILES = ['QUEEN.md'];
176
180
 
177
181
  // Target directories for git safety checks
178
182
  this.targetDirs = ['.aether', '.claude/commands/ant', '.claude/agents/ant', '.claude/rules', '.opencode/commands/ant', '.opencode/agents'];
183
+
184
+ // Managed paths for update safety checks (must align with files update can actually modify)
185
+ this.protectedAetherDirs = new Set([
186
+ 'data',
187
+ 'dreams',
188
+ 'oracle',
189
+ 'midden',
190
+ 'checkpoints',
191
+ 'locks',
192
+ 'temp',
193
+ 'archive',
194
+ 'chambers',
195
+ 'exchange',
196
+ ]);
197
+ this.managedPrefixes = [
198
+ '.claude/commands/ant',
199
+ '.claude/agents/ant',
200
+ '.claude/rules',
201
+ '.opencode/commands/ant',
202
+ '.opencode/agents',
203
+ ];
179
204
  }
180
205
 
181
206
  /**
@@ -210,7 +235,22 @@ class UpdateTransaction {
210
235
  */
211
236
  writeJsonSync(filePath, data) {
212
237
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
213
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
238
+ const content = JSON.stringify(data, null, 2) + '\n';
239
+ const tmpPath = filePath + '.tmp';
240
+ try {
241
+ fs.writeFileSync(tmpPath, content);
242
+ fs.renameSync(tmpPath, filePath);
243
+ } catch (err) {
244
+ // Clean up temp file on failure
245
+ try {
246
+ if (fs.existsSync(tmpPath)) {
247
+ fs.unlinkSync(tmpPath);
248
+ }
249
+ } catch {
250
+ // Ignore cleanup errors
251
+ }
252
+ throw err;
253
+ }
214
254
  }
215
255
 
216
256
  /**
@@ -228,6 +268,40 @@ class UpdateTransaction {
228
268
  }
229
269
  }
230
270
 
271
+ /**
272
+ * Copy file atomically using temp file + rename pattern
273
+ * Prevents partial file corruption on process interruption
274
+ * @param {string} srcPath - Source file path
275
+ * @param {string} destPath - Destination file path
276
+ * @private
277
+ */
278
+ copyFileAtomic(srcPath, destPath) {
279
+ const tempPath = `${destPath}.tmp.${process.pid}.${Date.now()}`;
280
+
281
+ try {
282
+ // Copy to temp file first
283
+ fs.copyFileSync(srcPath, tempPath);
284
+
285
+ // Atomic rename (POSIX guarantees atomicity)
286
+ fs.renameSync(tempPath, destPath);
287
+
288
+ // Set executable permission for shell scripts
289
+ if (destPath.endsWith('.sh')) {
290
+ fs.chmodSync(destPath, 0o755);
291
+ }
292
+ } catch (err) {
293
+ // Clean up temp file on failure
294
+ try {
295
+ if (fs.existsSync(tempPath)) {
296
+ fs.unlinkSync(tempPath);
297
+ }
298
+ } catch {
299
+ // Ignore cleanup errors
300
+ }
301
+ throw err;
302
+ }
303
+ }
304
+
231
305
  /**
232
306
  * Check if path is a git repository
233
307
  * @returns {boolean} True if git repo
@@ -242,6 +316,54 @@ class UpdateTransaction {
242
316
  }
243
317
  }
244
318
 
319
+ /**
320
+ * Normalize git porcelain path output.
321
+ * Handles rename syntax and quoted/escaped paths.
322
+ * @param {string} filePath - Raw path fragment from git porcelain
323
+ * @returns {string} Normalized path
324
+ * @private
325
+ */
326
+ normalizePorcelainPath(filePath) {
327
+ let normalized = filePath;
328
+
329
+ if (normalized.includes(' -> ')) {
330
+ normalized = normalized.split(' -> ').pop();
331
+ }
332
+
333
+ if (normalized.startsWith('"') && normalized.endsWith('"')) {
334
+ normalized = normalized
335
+ .slice(1, -1)
336
+ .replace(/\\"/g, '"')
337
+ .replace(/\\\\/g, '\\');
338
+ }
339
+
340
+ return normalized;
341
+ }
342
+
343
+ /**
344
+ * True when a path is managed by update and may be overwritten by sync.
345
+ * Paths under .aether/data and other protected runtime dirs are intentionally excluded.
346
+ * @param {string} filePath - Normalized or raw path
347
+ * @returns {boolean} Whether update should treat this path as managed
348
+ * @private
349
+ */
350
+ isManagedUpdatePath(filePath) {
351
+ const p = this.normalizePorcelainPath(filePath);
352
+
353
+ if (p === '.aether' || p.startsWith('.aether/')) {
354
+ const rel = p === '.aether' ? '' : p.slice('.aether/'.length);
355
+ if (!rel) return true;
356
+
357
+ const first = rel.split('/')[0];
358
+ if (!first || first.startsWith('.')) return false;
359
+ if (this.protectedAetherDirs.has(first)) return false;
360
+ if (rel === 'QUEEN.md') return false;
361
+ return true;
362
+ }
363
+
364
+ return this.managedPrefixes.some(prefix => p === prefix || p.startsWith(`${prefix}/`));
365
+ }
366
+
245
367
  /**
246
368
  * Detect dirty repository state with detailed categorization
247
369
  * @returns {object} Dirty state info: { isDirty, tracked, untracked, staged }
@@ -257,14 +379,25 @@ class UpdateTransaction {
257
379
  encoding: 'utf8',
258
380
  });
259
381
 
260
- const lines = result.trim().split('\n').filter(Boolean);
382
+ // Preserve the two-column porcelain status prefix on each line.
383
+ // Using `trim()` on the whole output would strip leading spaces from
384
+ // the first line and can misclassify unstaged vs staged changes.
385
+ const lines = result.split('\n').filter(line => line.trim().length > 0);
261
386
  const tracked = [];
262
387
  const untracked = [];
263
388
  const staged = [];
389
+ let managedCount = 0;
264
390
 
265
391
  for (const line of lines) {
266
392
  const status = line.slice(0, 2);
267
- const filePath = line.slice(3);
393
+ const rawPath = line.slice(3);
394
+ const filePath = this.normalizePorcelainPath(rawPath);
395
+
396
+ if (!this.isManagedUpdatePath(filePath)) {
397
+ continue;
398
+ }
399
+
400
+ managedCount++;
268
401
 
269
402
  // Staged changes (in index)
270
403
  if (status[0] !== ' ' && status[0] !== '?') {
@@ -281,7 +414,7 @@ class UpdateTransaction {
281
414
  }
282
415
 
283
416
  return {
284
- isDirty: lines.length > 0,
417
+ isDirty: managedCount > 0,
285
418
  tracked,
286
419
  untracked,
287
420
  staged,
@@ -580,10 +713,7 @@ class UpdateTransaction {
580
713
  }
581
714
 
582
715
  if (shouldCopy) {
583
- fs.copyFileSync(srcPath, destPath);
584
- if (relPath.endsWith('.sh')) {
585
- fs.chmodSync(destPath, 0o755);
586
- }
716
+ this.copyFileAtomic(srcPath, destPath);
587
717
  copied++;
588
718
  }
589
719
  } catch (err) {
@@ -653,7 +783,52 @@ class UpdateTransaction {
653
783
  */
654
784
  shouldExclude(relPath) {
655
785
  const parts = relPath.split(path.sep);
656
- return parts.some(part => this.EXCLUDE_DIRS.includes(part));
786
+ // Check for excluded directories
787
+ if (parts.some(part => this.EXCLUDE_DIRS.includes(part))) {
788
+ return true;
789
+ }
790
+ // Check for excluded files (QUEEN.md protection)
791
+ const basename = path.basename(relPath);
792
+ if (this.EXCLUDE_FILES.includes(basename)) {
793
+ return true;
794
+ }
795
+ return false;
796
+ }
797
+
798
+ /**
799
+ * Get files that the update would actually overwrite (hub version differs from repo version).
800
+ * Used by the dirty-file check to only warn about files that matter.
801
+ * @returns {string[]} Repo-relative paths of files that would be overwritten
802
+ */
803
+ getConflictingFiles() {
804
+ const conflicts = [];
805
+
806
+ // Helper: check a hub->repo dir pair for differing files
807
+ const checkDir = (hubDir, repoDir, prefix) => {
808
+ if (!fs.existsSync(hubDir)) return;
809
+ const srcFiles = this.listFilesRecursive(hubDir);
810
+ for (const relPath of srcFiles) {
811
+ if (this.shouldExclude(relPath)) continue;
812
+ const destPath = path.join(repoDir, relPath);
813
+ if (fs.existsSync(destPath)) {
814
+ const srcHash = this.hashFileSync(path.join(hubDir, relPath));
815
+ const destHash = this.hashFileSync(destPath);
816
+ if (srcHash !== destHash) {
817
+ conflicts.push(`${prefix}${relPath}`);
818
+ }
819
+ }
820
+ }
821
+ };
822
+
823
+ const repoAether = path.join(this.repoPath, '.aether');
824
+ checkDir(this.HUB_SYSTEM_DIR, repoAether, '.aether/');
825
+ checkDir(this.HUB_COMMANDS_CLAUDE, path.join(this.repoPath, '.claude', 'commands', 'ant'), '.claude/commands/ant/');
826
+ checkDir(this.HUB_COMMANDS_OPENCODE, path.join(this.repoPath, '.opencode', 'commands', 'ant'), '.opencode/commands/ant/');
827
+ checkDir(this.HUB_AGENTS, path.join(this.repoPath, '.opencode', 'agents'), '.opencode/agents/');
828
+ checkDir(this.HUB_AGENTS_CLAUDE, path.join(this.repoPath, '.claude', 'agents', 'ant'), '.claude/agents/ant/');
829
+ checkDir(this.HUB_RULES, path.join(this.repoPath, '.claude', 'rules'), '.claude/rules/');
830
+
831
+ return conflicts;
657
832
  }
658
833
 
659
834
  /**
@@ -699,28 +874,30 @@ class UpdateTransaction {
699
874
  const srcPath = path.join(srcDir, relPath);
700
875
  const destPath = path.join(destDir, relPath);
701
876
 
877
+ // Hash comparison: determine if copy is needed (runs for both dry-run and actual)
878
+ let shouldCopy = true;
879
+ if (fs.existsSync(destPath)) {
880
+ const srcHash = this.hashFileSync(srcPath);
881
+ const destHash = this.hashFileSync(destPath);
882
+ if (srcHash === destHash) {
883
+ shouldCopy = false;
884
+ skipped++;
885
+ }
886
+ }
887
+
702
888
  if (!dryRun) {
703
889
  fs.mkdirSync(path.dirname(destPath), { recursive: true });
704
890
 
705
- // Hash comparison
706
- let shouldCopy = true;
707
- if (fs.existsSync(destPath)) {
708
- const srcHash = this.hashFileSync(srcPath);
709
- const destHash = this.hashFileSync(destPath);
710
- if (srcHash === destHash) {
711
- shouldCopy = false;
712
- skipped++;
713
- }
891
+ if (shouldCopy) {
892
+ this.copyFileAtomic(srcPath, destPath);
893
+ copied++; // FIX: Only increment when actually copied
714
894
  }
715
-
895
+ } else {
896
+ // dry-run: count files that WOULD be copied
716
897
  if (shouldCopy) {
717
- fs.copyFileSync(srcPath, destPath);
718
- if (relPath.endsWith('.sh')) {
719
- fs.chmodSync(destPath, 0o755);
720
- }
898
+ copied++; // Count only files that would actually be copied
721
899
  }
722
900
  }
723
- copied++;
724
901
  }
725
902
 
726
903
  // Cleanup: remove files in dest that aren't in source
@@ -766,18 +943,75 @@ class UpdateTransaction {
766
943
  return { copied, removed, skipped };
767
944
  }
768
945
 
946
+ /**
947
+ * Move a file or directory to trash instead of deleting
948
+ * @param {string} itemPath - Path to file or directory
949
+ * @param {string} repoPath - Repository root for trash location
950
+ * @returns {boolean} True if moved to trash successfully
951
+ * @private
952
+ */
953
+ moveToTrash(itemPath, repoPath) {
954
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
955
+ const trashDir = path.join(repoPath, '.aether', '.trash', timestamp);
956
+ const basename = path.basename(itemPath);
957
+ const trashPath = path.join(trashDir, basename);
958
+
959
+ try {
960
+ fs.mkdirSync(trashDir, { recursive: true });
961
+ fs.renameSync(itemPath, trashPath);
962
+ return true;
963
+ } catch (err) {
964
+ // If rename fails (cross-device), fall back to copy + delete
965
+ try {
966
+ fs.mkdirSync(trashDir, { recursive: true });
967
+ if (fs.statSync(itemPath).isDirectory()) {
968
+ fs.cpSync(itemPath, path.join(trashDir, basename), { recursive: true });
969
+ fs.rmSync(itemPath, { recursive: true, force: true });
970
+ } else {
971
+ fs.copyFileSync(itemPath, path.join(trashDir, basename));
972
+ fs.unlinkSync(itemPath);
973
+ }
974
+ return true;
975
+ } catch (fallbackErr) {
976
+ return false;
977
+ }
978
+ }
979
+ }
980
+
769
981
  /**
770
982
  * Remove known stale directories and files left behind by pre-3.0.0 versions.
771
- * These paths were incorrectly distributed in earlier releases and must be
772
- * explicitly removed EXCLUDE_DIRS prevents NEW pollution but does not clean
773
- * items that already exist on disk.
983
+ *
984
+ * IMPORTANT: Removed items are moved to .aether/.trash/ NOT deleted.
985
+ * Users can inspect and manually clean trash when ready.
986
+ * Trash folders are timestamped for easy identification.
987
+ *
988
+ * Protected paths (never cleaned):
989
+ * - .aether/data/ - Colony state
990
+ * - .aether/dreams/ - Session notes
991
+ * - .aether/oracle/ - Research progress
992
+ * - .aether/midden/ - Failure tracking
993
+ * - .aether/QUEEN.md - User's wisdom file
774
994
  *
775
995
  * Idempotent: safe to call when items do not exist.
776
996
  *
777
997
  * @param {string} repoPath - Absolute path to the target repository root
778
- * @returns {{ cleaned: string[], failed: Array<{label: string, error: string}> }}
998
+ * @returns {{ cleaned: string[], failed: Array<{label: string, error: string}>, trashDir: string }}
779
999
  */
780
1000
  cleanupStaleAetherDirs(repoPath) {
1001
+ // Safety check: never clean protected directories
1002
+ const protectedDirs = ['data', 'dreams', 'oracle', 'midden'];
1003
+ const protectedFiles = ['QUEEN.md'];
1004
+
1005
+ // Verify none of our stale items are in protected paths
1006
+ const aetherDir = path.join(repoPath, '.aether');
1007
+ for (const dir of protectedDirs) {
1008
+ const protectedPath = path.join(aetherDir, dir);
1009
+ if (fs.existsSync(protectedPath)) {
1010
+ // Log that protected dir exists and will be preserved
1011
+ this.log?.(` Preserving protected directory: .aether/${dir}/`);
1012
+ }
1013
+ }
1014
+
781
1015
  const staleItems = [
782
1016
  {
783
1017
  path: path.join(repoPath, '.aether', 'agents'),
@@ -806,18 +1040,17 @@ class UpdateTransaction {
806
1040
  }
807
1041
 
808
1042
  try {
809
- if (item.type === 'dir') {
810
- fs.rmSync(item.path, { recursive: true, force: true });
1043
+ if (this.moveToTrash(item.path, repoPath)) {
1044
+ cleaned.push(item.label);
811
1045
  } else {
812
- fs.unlinkSync(item.path);
1046
+ failed.push({ label: item.label, error: 'Failed to move to trash' });
813
1047
  }
814
- cleaned.push(item.label);
815
1048
  } catch (err) {
816
1049
  failed.push({ label: item.label, error: err.message });
817
1050
  }
818
1051
  }
819
1052
 
820
- return { cleaned, failed };
1053
+ return { cleaned, failed, trashDir: path.join(repoPath, '.aether', '.trash') };
821
1054
  }
822
1055
 
823
1056
  /**
@@ -1250,6 +1483,23 @@ class UpdateTransaction {
1250
1483
  return false;
1251
1484
  }
1252
1485
 
1486
+ // Restore backed-up managed files before stash pop
1487
+ // Stash only contains the user's local modifications; managed files
1488
+ // overwritten by syncFiles must be restored from the checkpoint backup.
1489
+ if (this.checkpoint.backedUpFiles && this.checkpoint.backedUpFiles.length > 0) {
1490
+ for (const entry of this.checkpoint.backedUpFiles) {
1491
+ try {
1492
+ if (fs.existsSync(entry.backupPath)) {
1493
+ fs.mkdirSync(path.dirname(path.join(this.repoPath, entry.relPath)), { recursive: true });
1494
+ fs.copyFileSync(entry.backupPath, path.join(this.repoPath, entry.relPath));
1495
+ this.log(` Restored managed file: ${entry.relPath}`);
1496
+ }
1497
+ } catch (err) {
1498
+ this.log(` Warning: could not restore ${entry.relPath}: ${err.message}`);
1499
+ }
1500
+ }
1501
+ }
1502
+
1253
1503
  // Restore from stash if available
1254
1504
  if (this.checkpoint.stashRef) {
1255
1505
  try {
@@ -1451,4 +1701,4 @@ module.exports = {
1451
1701
  UpdateError,
1452
1702
  UpdateErrorCodes,
1453
1703
  TransactionStates,
1454
- };
1704
+ };
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * npx-install.js — Professional installer for Aether Colony
5
+ *
6
+ * Usage: npx aether-colony install
7
+ *
8
+ * Creates the global hub at ~/.aether/ with all system files,
9
+ * slash commands, and agent definitions.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+
16
+ const BANNER = `
17
+ █████╗ ███████╗████████╗██╗ ██╗███████╗██████╗
18
+ ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔════╝██╔══██╗
19
+ ███████║█████╗ ██║ ███████║█████╗ ██████╔╝
20
+ ██╔══██║██╔══╝ ██║ ██╔══██║██╔══╝ ██╔══██╗
21
+ ██║ ██║███████╗ ██║ ██║ ██║███████╗██║ ██║
22
+ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
23
+ `;
24
+
25
+ const AETHER_VERSION = require('../package.json').version;
26
+ const HOME_DIR = os.homedir();
27
+ const HUB_DIR = path.join(HOME_DIR, '.aether');
28
+ const CLAUDE_COMMANDS_DIR = path.join(HOME_DIR, '.claude', 'commands', 'ant');
29
+ const CLAUDE_AGENTS_DIR = path.join(HOME_DIR, '.claude', 'agents', 'ant');
30
+
31
+ // Get the package root (where this script is located)
32
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
33
+ const AETHER_SRC = path.join(PACKAGE_ROOT, '.aether');
34
+ const CLAUDE_COMMANDS_SRC = path.join(PACKAGE_ROOT, '.claude', 'commands', 'ant');
35
+ const OPENCODE_AGENTS_SRC = path.join(PACKAGE_ROOT, '.opencode', 'agents');
36
+ const OPENCODE_COMMANDS_SRC = path.join(PACKAGE_ROOT, '.opencode', 'commands', 'ant');
37
+
38
+ function log(message, type = 'info') {
39
+ const icons = {
40
+ info: 'ℹ',
41
+ success: '✓',
42
+ warning: '⚠',
43
+ error: '✗',
44
+ ant: '🐜'
45
+ };
46
+ console.log(`${icons[type] || '•'} ${message}`);
47
+ }
48
+
49
+ function ensureDir(dir) {
50
+ if (!fs.existsSync(dir)) {
51
+ fs.mkdirSync(dir, { recursive: true });
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+
57
+ function copyDir(src, dest, options = {}) {
58
+ const { exclude = [] } = options;
59
+ ensureDir(dest);
60
+
61
+ const entries = fs.readdirSync(src, { withFileTypes: true });
62
+ let copied = 0;
63
+
64
+ for (const entry of entries) {
65
+ const srcPath = path.join(src, entry.name);
66
+ const destPath = path.join(dest, entry.name);
67
+
68
+ // Skip excluded directories
69
+ if (exclude.includes(entry.name)) {
70
+ continue;
71
+ }
72
+
73
+ if (entry.isDirectory()) {
74
+ copied += copyDir(srcPath, destPath, options);
75
+ } else {
76
+ fs.copyFileSync(srcPath, destPath);
77
+ copied++;
78
+ }
79
+ }
80
+
81
+ return copied;
82
+ }
83
+
84
+ function copyFile(src, dest) {
85
+ const destDir = path.dirname(dest);
86
+ ensureDir(destDir);
87
+ fs.copyFileSync(src, dest);
88
+ }
89
+
90
+ function install() {
91
+ console.log(BANNER);
92
+ console.log('\n');
93
+
94
+ let filesCopied = 0;
95
+
96
+ // Step 1: Create hub directory structure
97
+ log('Creating hub directory structure...', 'ant');
98
+ const hubDirs = [
99
+ path.join(HUB_DIR, 'system'),
100
+ path.join(HUB_DIR, 'system', 'docs'),
101
+ path.join(HUB_DIR, 'system', 'utils'),
102
+ path.join(HUB_DIR, 'system', 'templates'),
103
+ path.join(HUB_DIR, 'system', 'schemas'),
104
+ path.join(HUB_DIR, 'system', 'exchange'),
105
+ path.join(HUB_DIR, 'system', 'rules'),
106
+ path.join(HUB_DIR, 'data'),
107
+ path.join(HUB_DIR, 'chambers')
108
+ ];
109
+
110
+ for (const dir of hubDirs) {
111
+ if (ensureDir(dir)) {
112
+ log(` Created ${path.relative(HOME_DIR, dir)}`, 'info');
113
+ }
114
+ }
115
+
116
+ // Step 2: Copy system files from .aether/
117
+ log('Copying system files to hub...', 'ant');
118
+ if (fs.existsSync(AETHER_SRC)) {
119
+ // Private directories to exclude
120
+ const excludeDirs = ['data', 'dreams', 'oracle', 'checkpoints', 'locks', 'temp', 'archive', 'chambers'];
121
+ filesCopied += copyDir(AETHER_SRC, path.join(HUB_DIR, 'system'), { exclude: excludeDirs });
122
+ log(` Copied ${filesCopied} files from .aether/`, 'success');
123
+ } else {
124
+ log(' Warning: .aether/ source not found', 'warning');
125
+ }
126
+
127
+ // Step 3: Copy Claude Code commands
128
+ log('Installing Claude Code commands...', 'ant');
129
+ if (fs.existsSync(CLAUDE_COMMANDS_SRC)) {
130
+ const cmdCount = copyDir(CLAUDE_COMMANDS_SRC, CLAUDE_COMMANDS_DIR);
131
+ log(` Installed ${cmdCount} slash commands to ~/.claude/commands/ant/`, 'success');
132
+ filesCopied += cmdCount;
133
+ }
134
+
135
+ // Step 4: Copy Claude Code agents (from OpenCode agents)
136
+ log('Installing Claude Code agents...', 'ant');
137
+ if (fs.existsSync(OPENCODE_AGENTS_SRC)) {
138
+ const agentCount = copyDir(OPENCODE_AGENTS_SRC, CLAUDE_AGENTS_DIR);
139
+ log(` Installed ${agentCount} agents to ~/.claude/agents/ant/`, 'success');
140
+ filesCopied += agentCount;
141
+ }
142
+
143
+ // Step 5: Write version file
144
+ const versionData = {
145
+ version: AETHER_VERSION,
146
+ installed_at: new Date().toISOString(),
147
+ package_root: PACKAGE_ROOT
148
+ };
149
+ fs.writeFileSync(
150
+ path.join(HUB_DIR, 'version.json'),
151
+ JSON.stringify(versionData, null, 2)
152
+ );
153
+ log(' Version info written', 'success');
154
+
155
+ // Step 6: Create global QUEEN.md if missing
156
+ const globalQueen = path.join(HUB_DIR, 'QUEEN.md');
157
+ if (!fs.existsSync(globalQueen)) {
158
+ const queenTemplate = path.join(HUB_DIR, 'system', 'templates', 'QUEEN.md.template');
159
+ if (fs.existsSync(queenTemplate)) {
160
+ let content = fs.readFileSync(queenTemplate, 'utf8');
161
+ content = content.replace(/{TIMESTAMP}/g, new Date().toISOString());
162
+ fs.writeFileSync(globalQueen, content);
163
+ log(' Created global QUEEN.md', 'success');
164
+ }
165
+ }
166
+
167
+ // Summary
168
+ console.log('\n ─────────────────────────────────────────────\n');
169
+ log(`Installation complete! ${filesCopied} files installed.`, 'success');
170
+ console.log('\n Next steps:\n');
171
+ console.log(' 1. Run /ant:init "your goal" in any project');
172
+ console.log(' 2. Use /ant:build to execute phases');
173
+ console.log(' 3. Run /ant:help for command reference\n');
174
+ console.log(' ─────────────────────────────────────────────\n');
175
+ }
176
+
177
+ // Run installer
178
+ install();