aether-colony 3.1.17 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.aether/aether-utils.sh +5354 -0
- package/.aether/agents-claude/aether-ambassador.md +265 -0
- package/.aether/agents-claude/aether-archaeologist.md +327 -0
- package/.aether/agents-claude/aether-architect.md +236 -0
- package/.aether/agents-claude/aether-auditor.md +271 -0
- package/.aether/agents-claude/aether-builder.md +224 -0
- package/.aether/agents-claude/aether-chaos.md +269 -0
- package/.aether/agents-claude/aether-chronicler.md +305 -0
- package/.aether/agents-claude/aether-gatekeeper.md +330 -0
- package/.aether/agents-claude/aether-includer.md +374 -0
- package/.aether/agents-claude/aether-keeper.md +272 -0
- package/.aether/agents-claude/aether-measurer.md +322 -0
- package/.aether/agents-claude/aether-oracle.md +237 -0
- package/.aether/agents-claude/aether-probe.md +211 -0
- package/.aether/agents-claude/aether-queen.md +330 -0
- package/.aether/agents-claude/aether-route-setter.md +178 -0
- package/.aether/agents-claude/aether-sage.md +418 -0
- package/.aether/agents-claude/aether-scout.md +179 -0
- package/.aether/agents-claude/aether-surveyor-disciplines.md +417 -0
- package/.aether/agents-claude/aether-surveyor-nest.md +355 -0
- package/.aether/agents-claude/aether-surveyor-pathogens.md +289 -0
- package/.aether/agents-claude/aether-surveyor-provisions.md +360 -0
- package/.aether/agents-claude/aether-tracker.md +270 -0
- package/.aether/agents-claude/aether-watcher.md +280 -0
- package/.aether/agents-claude/aether-weaver.md +248 -0
- package/.aether/commands/archaeology.yaml +653 -0
- package/.aether/commands/build.yaml +1221 -0
- package/.aether/commands/chaos.yaml +653 -0
- package/.aether/commands/colonize.yaml +438 -0
- package/.aether/commands/continue.yaml +1484 -0
- package/.aether/commands/council.yaml +304 -0
- package/.aether/commands/data-clean.yaml +80 -0
- package/.aether/commands/dream.yaml +275 -0
- package/.aether/commands/entomb.yaml +863 -0
- package/.aether/commands/export-signals.yaml +64 -0
- package/.aether/commands/feedback.yaml +158 -0
- package/.aether/commands/flag.yaml +160 -0
- package/.aether/commands/flags.yaml +177 -0
- package/.aether/commands/focus.yaml +112 -0
- package/.aether/commands/help.yaml +167 -0
- package/.aether/commands/history.yaml +137 -0
- package/.aether/commands/import-signals.yaml +79 -0
- package/.aether/commands/init.yaml +469 -0
- package/.aether/commands/insert-phase.yaml +98 -0
- package/.aether/commands/interpret.yaml +285 -0
- package/.aether/commands/lay-eggs.yaml +224 -0
- package/.aether/commands/maturity.yaml +122 -0
- package/.aether/commands/memory-details.yaml +74 -0
- package/.aether/commands/migrate-state.yaml +174 -0
- package/.aether/commands/oracle.yaml +1224 -0
- package/.aether/commands/organize.yaml +446 -0
- package/.aether/commands/patrol.yaml +621 -0
- package/.aether/commands/pause-colony.yaml +424 -0
- package/.aether/commands/phase.yaml +124 -0
- package/.aether/commands/pheromones.yaml +153 -0
- package/.aether/commands/plan.yaml +1313 -0
- package/.aether/commands/preferences.yaml +63 -0
- package/.aether/commands/redirect.yaml +123 -0
- package/.aether/commands/resume-colony.yaml +373 -0
- package/.aether/commands/resume.yaml +398 -0
- package/.aether/commands/run.yaml +193 -0
- package/.aether/commands/seal.yaml +1205 -0
- package/.aether/commands/skill-create.yaml +337 -0
- package/.aether/commands/status.yaml +364 -0
- package/.aether/commands/swarm.yaml +352 -0
- package/.aether/commands/tunnels.yaml +814 -0
- package/.aether/commands/update.yaml +131 -0
- package/.aether/commands/verify-castes.yaml +159 -0
- package/.aether/commands/watch.yaml +454 -0
- package/.aether/docs/INCIDENT_TEMPLATE.md +32 -0
- package/.aether/docs/QUEEN-SYSTEM.md +211 -0
- package/.aether/docs/README.md +98 -0
- package/.aether/docs/caste-system.md +48 -0
- package/.aether/docs/command-playbooks/README.md +23 -0
- package/.aether/docs/command-playbooks/build-complete.md +349 -0
- package/.aether/docs/command-playbooks/build-context.md +282 -0
- package/.aether/docs/command-playbooks/build-full.md +1682 -0
- package/.aether/docs/command-playbooks/build-prep.md +283 -0
- package/.aether/docs/command-playbooks/build-verify.md +405 -0
- package/.aether/docs/command-playbooks/build-wave.md +749 -0
- package/.aether/docs/command-playbooks/continue-advance.md +524 -0
- package/.aether/docs/command-playbooks/continue-finalize.md +447 -0
- package/.aether/docs/command-playbooks/continue-full.md +1724 -0
- package/.aether/docs/command-playbooks/continue-gates.md +686 -0
- package/.aether/docs/command-playbooks/continue-verify.md +406 -0
- package/.aether/docs/context-continuity.md +84 -0
- package/{runtime → .aether/docs/disciplines}/DISCIPLINES.md +13 -11
- package/.aether/docs/error-codes.md +268 -0
- package/.aether/docs/known-issues.md +94 -0
- package/{runtime → .aether}/docs/pheromones.md +86 -6
- package/.aether/docs/plans/pheromone-display-plan.md +257 -0
- package/.aether/docs/queen-commands.md +98 -0
- package/.aether/docs/source-of-truth-map.md +132 -0
- package/.aether/docs/xml-utilities.md +47 -0
- package/{runtime → .aether}/exchange/pheromone-xml.sh +2 -1
- package/{runtime → .aether}/exchange/registry-xml.sh +7 -3
- package/{runtime → .aether}/exchange/wisdom-xml.sh +11 -4
- package/.aether/rules/aether-colony.md +144 -0
- package/.aether/schemas/example-prompt-builder.xml +234 -0
- package/.aether/scripts/incident-test-add.sh +47 -0
- package/.aether/scripts/weekly-audit.sh +79 -0
- package/.aether/skills/.index.json +649 -0
- package/.aether/skills/colony/.manifest.json +16 -0
- package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
- package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
- package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
- package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
- package/.aether/skills/colony/context-management/SKILL.md +80 -0
- package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
- package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
- package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
- package/.aether/skills/colony/state-safety/SKILL.md +84 -0
- package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
- package/.aether/skills/domain/.manifest.json +24 -0
- package/.aether/skills/domain/README.md +33 -0
- package/.aether/skills/domain/django/SKILL.md +49 -0
- package/.aether/skills/domain/docker/SKILL.md +52 -0
- package/.aether/skills/domain/golang/SKILL.md +52 -0
- package/.aether/skills/domain/graphql/SKILL.md +51 -0
- package/.aether/skills/domain/html-css/SKILL.md +48 -0
- package/.aether/skills/domain/nextjs/SKILL.md +45 -0
- package/.aether/skills/domain/nodejs/SKILL.md +53 -0
- package/.aether/skills/domain/postgresql/SKILL.md +53 -0
- package/.aether/skills/domain/prisma/SKILL.md +59 -0
- package/.aether/skills/domain/python/SKILL.md +50 -0
- package/.aether/skills/domain/rails/SKILL.md +52 -0
- package/.aether/skills/domain/react/SKILL.md +45 -0
- package/.aether/skills/domain/rest-api/SKILL.md +58 -0
- package/.aether/skills/domain/svelte/SKILL.md +47 -0
- package/.aether/skills/domain/tailwind/SKILL.md +45 -0
- package/.aether/skills/domain/testing/SKILL.md +53 -0
- package/.aether/skills/domain/typescript/SKILL.md +58 -0
- package/.aether/skills/domain/vue/SKILL.md +49 -0
- package/.aether/templates/QUEEN.md.template +61 -0
- package/.aether/templates/colony-state-reset.jq.template +23 -0
- package/.aether/templates/colony-state.template.json +39 -0
- package/.aether/templates/constraints.template.json +9 -0
- package/.aether/templates/crowned-anthill.template.md +36 -0
- package/.aether/templates/handoff-build-error.template.md +30 -0
- package/.aether/templates/handoff-build-success.template.md +39 -0
- package/.aether/templates/handoff.template.md +40 -0
- package/.aether/templates/learning-observations.template.json +6 -0
- package/.aether/templates/midden.template.json +13 -0
- package/.aether/templates/pheromones.template.json +6 -0
- package/.aether/templates/session.template.json +9 -0
- package/{runtime → .aether}/utils/atomic-write.sh +68 -22
- package/{runtime → .aether}/utils/chamber-compare.sh +23 -10
- package/.aether/utils/chamber-utils.sh +440 -0
- package/.aether/utils/emoji-audit.sh +166 -0
- package/{runtime → .aether}/utils/error-handler.sh +34 -8
- package/.aether/utils/file-lock.sh +313 -0
- package/.aether/utils/flag.sh +267 -0
- package/.aether/utils/hive.sh +572 -0
- package/.aether/utils/learning.sh +1928 -0
- package/.aether/utils/midden.sh +342 -0
- package/.aether/utils/oracle/oracle.md +168 -0
- package/.aether/utils/oracle/oracle.sh +1023 -0
- package/.aether/utils/pheromone.sh +2029 -0
- package/.aether/utils/queen.sh +1698 -0
- package/.aether/utils/scan.sh +860 -0
- package/.aether/utils/semantic-cli.sh +415 -0
- package/.aether/utils/session.sh +552 -0
- package/.aether/utils/skills.sh +509 -0
- package/.aether/utils/spawn-tree.sh +260 -0
- package/.aether/utils/spawn.sh +260 -0
- package/.aether/utils/state-api.sh +199 -0
- package/{runtime → .aether}/utils/state-loader.sh +8 -6
- package/.aether/utils/suggest.sh +611 -0
- package/{runtime → .aether}/utils/swarm-display.sh +10 -1
- package/.aether/utils/swarm.sh +1004 -0
- package/{runtime → .aether}/utils/watch-spawn-tree.sh +11 -2
- package/{runtime → .aether}/utils/xml-compose.sh +9 -3
- package/.aether/utils/xml-convert.sh +277 -0
- package/{runtime → .aether}/utils/xml-core.sh +5 -9
- package/.aether/utils/xml-query.sh +201 -0
- package/.aether/utils/xml-utils.sh +110 -0
- package/{runtime → .aether}/workers.md +97 -81
- package/.claude/agents/ant/aether-ambassador.md +265 -0
- package/.claude/agents/ant/aether-archaeologist.md +327 -0
- package/.claude/agents/ant/aether-architect.md +236 -0
- package/.claude/agents/ant/aether-auditor.md +271 -0
- package/.claude/agents/ant/aether-builder.md +224 -0
- package/.claude/agents/ant/aether-chaos.md +269 -0
- package/.claude/agents/ant/aether-chronicler.md +305 -0
- package/.claude/agents/ant/aether-gatekeeper.md +330 -0
- package/.claude/agents/ant/aether-includer.md +374 -0
- package/.claude/agents/ant/aether-keeper.md +272 -0
- package/.claude/agents/ant/aether-measurer.md +322 -0
- package/.claude/agents/ant/aether-oracle.md +237 -0
- package/.claude/agents/ant/aether-probe.md +211 -0
- package/.claude/agents/ant/aether-queen.md +330 -0
- package/.claude/agents/ant/aether-route-setter.md +178 -0
- package/.claude/agents/ant/aether-sage.md +418 -0
- package/.claude/agents/ant/aether-scout.md +179 -0
- package/.claude/agents/ant/aether-surveyor-disciplines.md +417 -0
- package/.claude/agents/ant/aether-surveyor-nest.md +355 -0
- package/.claude/agents/ant/aether-surveyor-pathogens.md +289 -0
- package/.claude/agents/ant/aether-surveyor-provisions.md +360 -0
- package/.claude/agents/ant/aether-tracker.md +270 -0
- package/.claude/agents/ant/aether-watcher.md +280 -0
- package/.claude/agents/ant/aether-weaver.md +248 -0
- package/.claude/commands/ant/archaeology.md +16 -14
- package/.claude/commands/ant/build.md +43 -1028
- package/.claude/commands/ant/chaos.md +19 -23
- package/.claude/commands/ant/colonize.md +52 -31
- package/.claude/commands/ant/continue.md +40 -1016
- package/.claude/commands/ant/council.md +21 -18
- package/.claude/commands/ant/data-clean.md +81 -0
- package/.claude/commands/ant/dream.md +27 -15
- package/.claude/commands/ant/entomb.md +317 -225
- package/.claude/commands/ant/export-signals.md +57 -0
- package/.claude/commands/ant/feedback.md +48 -26
- package/.claude/commands/ant/flag.md +30 -10
- package/.claude/commands/ant/flags.md +34 -12
- package/.claude/commands/ant/focus.md +45 -19
- package/.claude/commands/ant/help.md +50 -8
- package/.claude/commands/ant/history.md +13 -0
- package/.claude/commands/ant/import-signals.md +71 -0
- package/.claude/commands/ant/init.md +345 -194
- package/.claude/commands/ant/insert-phase.md +101 -0
- package/.claude/commands/ant/interpret.md +26 -4
- package/.claude/commands/ant/lay-eggs.md +184 -127
- package/.claude/commands/ant/maturity.md +32 -11
- package/.claude/commands/ant/memory-details.md +77 -0
- package/.claude/commands/ant/migrate-state.md +20 -2
- package/.claude/commands/ant/oracle.md +337 -74
- package/.claude/commands/ant/organize.md +39 -25
- package/.claude/commands/ant/patrol.md +620 -0
- package/.claude/commands/ant/pause-colony.md +23 -27
- package/.claude/commands/ant/phase.md +40 -42
- package/.claude/commands/ant/pheromones.md +156 -0
- package/.claude/commands/ant/plan.md +185 -51
- package/.claude/commands/ant/preferences.md +65 -0
- package/.claude/commands/ant/redirect.md +45 -30
- package/.claude/commands/ant/resume-colony.md +51 -27
- package/.claude/commands/ant/resume.md +314 -94
- package/.claude/commands/ant/run.md +195 -0
- package/.claude/commands/ant/seal.md +650 -221
- package/.claude/commands/ant/skill-create.md +286 -0
- package/.claude/commands/ant/status.md +196 -31
- package/.claude/commands/ant/swarm.md +16 -46
- package/.claude/commands/ant/tunnels.md +280 -105
- package/.claude/commands/ant/update.md +73 -89
- package/.claude/commands/ant/verify-castes.md +100 -42
- package/.claude/commands/ant/watch.md +14 -12
- package/.opencode/agents/aether-ambassador.md +63 -20
- package/.opencode/agents/aether-archaeologist.md +29 -12
- package/.opencode/agents/aether-architect.md +103 -36
- package/.opencode/agents/aether-auditor.md +51 -18
- package/.opencode/agents/aether-builder.md +70 -20
- package/.opencode/agents/aether-chaos.md +29 -12
- package/.opencode/agents/aether-chronicler.md +60 -18
- package/.opencode/agents/aether-gatekeeper.md +27 -18
- package/.opencode/agents/aether-includer.md +27 -18
- package/.opencode/agents/aether-keeper.md +89 -18
- package/.opencode/agents/aether-measurer.md +27 -18
- package/.opencode/agents/aether-oracle.md +137 -0
- package/.opencode/agents/aether-probe.md +60 -18
- package/.opencode/agents/aether-queen.md +172 -24
- package/.opencode/agents/aether-route-setter.md +57 -12
- package/.opencode/agents/aether-sage.md +26 -18
- package/.opencode/agents/aether-scout.md +28 -20
- package/.opencode/agents/aether-surveyor-disciplines.md +59 -2
- package/.opencode/agents/aether-surveyor-nest.md +59 -2
- package/.opencode/agents/aether-surveyor-pathogens.md +57 -2
- package/.opencode/agents/aether-surveyor-provisions.md +59 -2
- package/.opencode/agents/aether-tracker.md +64 -18
- package/.opencode/agents/aether-watcher.md +66 -19
- package/.opencode/agents/aether-weaver.md +61 -18
- package/.opencode/commands/ant/archaeology.md +7 -14
- package/.opencode/commands/ant/build.md +437 -257
- package/.opencode/commands/ant/chaos.md +7 -24
- package/.opencode/commands/ant/colonize.md +8 -17
- package/.opencode/commands/ant/continue.md +661 -142
- package/.opencode/commands/ant/council.md +11 -22
- package/.opencode/commands/ant/data-clean.md +77 -0
- package/.opencode/commands/ant/dream.md +15 -17
- package/.opencode/commands/ant/entomb.md +133 -62
- package/.opencode/commands/ant/export-signals.md +54 -0
- package/.opencode/commands/ant/feedback.md +24 -5
- package/.opencode/commands/ant/flag.md +16 -4
- package/.opencode/commands/ant/flags.md +24 -10
- package/.opencode/commands/ant/focus.md +22 -5
- package/.opencode/commands/ant/help.md +41 -8
- package/.opencode/commands/ant/history.md +9 -0
- package/.opencode/commands/ant/import-signals.md +68 -0
- package/.opencode/commands/ant/init.md +374 -167
- package/.opencode/commands/ant/insert-phase.md +107 -0
- package/.opencode/commands/ant/interpret.md +16 -0
- package/.opencode/commands/ant/lay-eggs.md +184 -112
- package/.opencode/commands/ant/maturity.md +18 -2
- package/.opencode/commands/ant/memory-details.md +83 -0
- package/.opencode/commands/ant/migrate-state.md +12 -0
- package/.opencode/commands/ant/oracle.md +322 -67
- package/.opencode/commands/ant/organize.md +18 -16
- package/.opencode/commands/ant/patrol.md +626 -0
- package/.opencode/commands/ant/pause-colony.md +12 -29
- package/.opencode/commands/ant/phase.md +30 -40
- package/.opencode/commands/ant/pheromones.md +162 -0
- package/.opencode/commands/ant/plan.md +184 -56
- package/.opencode/commands/ant/preferences.md +71 -0
- package/.opencode/commands/ant/redirect.md +22 -5
- package/.opencode/commands/ant/resume-colony.md +38 -27
- package/.opencode/commands/ant/resume.md +385 -0
- package/.opencode/commands/ant/run.md +201 -0
- package/.opencode/commands/ant/seal.md +259 -45
- package/.opencode/commands/ant/skill-create.md +63 -0
- package/.opencode/commands/ant/status.md +135 -31
- package/.opencode/commands/ant/swarm.md +3 -345
- package/.opencode/commands/ant/tunnels.md +152 -9
- package/.opencode/commands/ant/update.md +70 -91
- package/.opencode/commands/ant/verify-castes.md +96 -42
- package/.opencode/commands/ant/watch.md +7 -0
- package/CHANGELOG.md +356 -0
- package/README.md +203 -573
- package/bin/cli.js +455 -527
- package/bin/generate-commands.js +186 -0
- package/bin/generate-commands.sh +127 -88
- package/bin/lib/init.js +13 -3
- package/bin/lib/spawn-logger.js +0 -15
- package/bin/lib/update-transaction.js +392 -140
- package/bin/npx-install.js +178 -0
- package/bin/sync-to-runtime.sh +5 -137
- package/bin/validate-package.sh +166 -0
- package/package.json +14 -7
- package/.opencode/agents/aether-guardian.md +0 -107
- package/.opencode/agents/workers.md +0 -1034
- package/bin/lib/model-profiles.js +0 -445
- package/bin/lib/model-verify.js +0 -288
- package/bin/lib/proxy-health.js +0 -253
- package/bin/lib/telemetry.js +0 -441
- package/runtime/CONTEXT.md +0 -160
- package/runtime/QUEEN_ANT_ARCHITECTURE.md +0 -402
- package/runtime/aether-utils.sh +0 -3879
- package/runtime/data/signatures.json +0 -41
- package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +0 -1343
- package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +0 -2642
- package/runtime/docs/PHEROMONE-INJECTION.md +0 -240
- package/runtime/docs/PHEROMONE-INTEGRATION.md +0 -192
- package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +0 -426
- package/runtime/docs/README.md +0 -94
- package/runtime/docs/VISUAL-OUTPUT-SPEC.md +0 -219
- package/runtime/docs/biological-reference.md +0 -272
- package/runtime/docs/codebase-review.md +0 -399
- package/runtime/docs/command-sync.md +0 -164
- package/runtime/docs/constraints.md +0 -116
- package/runtime/docs/implementation-learnings.md +0 -89
- package/runtime/docs/known-issues.md +0 -217
- package/runtime/docs/namespace.md +0 -148
- package/runtime/docs/pathogen-schema-example.json +0 -36
- package/runtime/docs/pathogen-schema.md +0 -111
- package/runtime/docs/planning-discipline.md +0 -159
- package/runtime/docs/progressive-disclosure.md +0 -184
- package/runtime/lib/queen-utils.sh +0 -729
- package/runtime/model-profiles.yaml +0 -100
- package/runtime/planning.md +0 -159
- package/runtime/recover.sh +0 -136
- package/runtime/templates/QUEEN.md.template +0 -79
- package/runtime/utils/chamber-utils.sh +0 -285
- package/runtime/utils/file-lock.sh +0 -122
- package/runtime/utils/spawn-tree.sh +0 -428
- package/runtime/utils/spawn-with-model.sh +0 -56
- package/runtime/utils/xml-utils.sh +0 -2196
- package/runtime/workers-new-castes.md +0 -516
- /package/{runtime → .aether/docs/disciplines}/coding-standards.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/debugging.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/learning.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/tdd.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/verification-loop.md +0 -0
- /package/{runtime → .aether/docs/disciplines}/verification.md +0 -0
- /package/{runtime → .aether}/schemas/aether-types.xsd +0 -0
- /package/{runtime → .aether}/schemas/colony-registry.xsd +0 -0
- /package/{runtime → .aether}/schemas/pheromone.xsd +0 -0
- /package/{runtime → .aether}/schemas/prompt.xsd +0 -0
- /package/{runtime → .aether}/schemas/queen-wisdom.xsd +0 -0
- /package/{runtime → .aether}/schemas/worker-priming.xsd +0 -0
- /package/{runtime → .aether}/utils/colorize-log.sh +0 -0
- /package/{runtime → .aether}/utils/queen-to-md.xsl +0 -0
|
@@ -161,61 +161,45 @@ class UpdateTransaction {
|
|
|
161
161
|
// Hub paths (from cli.js)
|
|
162
162
|
this.HOME = process.env.HOME || process.env.USERPROFILE;
|
|
163
163
|
this.HUB_DIR = path.join(this.HOME, '.aether');
|
|
164
|
-
this.
|
|
165
|
-
this.
|
|
166
|
-
this.
|
|
164
|
+
this.HUB_SYSTEM_DIR = path.join(this.HUB_DIR, 'system');
|
|
165
|
+
this.HUB_COMMANDS_CLAUDE = path.join(this.HUB_SYSTEM_DIR, 'commands', 'claude');
|
|
166
|
+
this.HUB_COMMANDS_OPENCODE = path.join(this.HUB_SYSTEM_DIR, 'commands', 'opencode');
|
|
167
|
+
this.HUB_AGENTS = path.join(this.HUB_SYSTEM_DIR, 'agents');
|
|
168
|
+
this.HUB_AGENTS_CLAUDE = path.join(this.HUB_SYSTEM_DIR, 'agents-claude');
|
|
169
|
+
this.HUB_RULES = path.join(this.HUB_SYSTEM_DIR, 'rules');
|
|
167
170
|
this.HUB_VERSION = path.join(this.HUB_DIR, 'version.json');
|
|
168
171
|
this.HUB_REGISTRY = path.join(this.HUB_DIR, 'registry.json');
|
|
169
172
|
|
|
170
|
-
// Directories to exclude from sync (user data, local state)
|
|
171
|
-
|
|
173
|
+
// Directories to exclude from sync (user data, local state, and separately-synced dirs)
|
|
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'];
|
|
177
|
+
|
|
178
|
+
// Files to exclude from sync (user wisdom, protected files)
|
|
179
|
+
this.EXCLUDE_FILES = ['QUEEN.md'];
|
|
172
180
|
|
|
173
181
|
// Target directories for git safety checks
|
|
174
|
-
this.targetDirs = ['.aether', '.claude/commands/ant', '.opencode/commands/ant', '.opencode/agents'];
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
this.
|
|
178
|
-
'
|
|
179
|
-
'
|
|
180
|
-
'
|
|
181
|
-
'
|
|
182
|
-
'
|
|
183
|
-
'
|
|
184
|
-
'
|
|
185
|
-
'
|
|
186
|
-
'
|
|
187
|
-
'
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
'
|
|
191
|
-
'
|
|
192
|
-
'
|
|
193
|
-
'
|
|
194
|
-
'
|
|
195
|
-
'docs/pathogen-schema.md',
|
|
196
|
-
'docs/PHEROMONE-INJECTION.md',
|
|
197
|
-
'docs/PHEROMONE-INTEGRATION.md',
|
|
198
|
-
'docs/PHEROMONE-SYSTEM-DESIGN.md',
|
|
199
|
-
'docs/pheromones.md',
|
|
200
|
-
'docs/progressive-disclosure.md',
|
|
201
|
-
'docs/README.md',
|
|
202
|
-
'docs/VISUAL-OUTPUT-SPEC.md',
|
|
203
|
-
'docs/known-issues.md',
|
|
204
|
-
'docs/implementation-learnings.md',
|
|
205
|
-
'docs/codebase-review.md',
|
|
206
|
-
'docs/planning-discipline.md',
|
|
207
|
-
'utils/atomic-write.sh',
|
|
208
|
-
'utils/chamber-compare.sh',
|
|
209
|
-
'utils/chamber-utils.sh',
|
|
210
|
-
'utils/colorize-log.sh',
|
|
211
|
-
'utils/error-handler.sh',
|
|
212
|
-
'utils/file-lock.sh',
|
|
213
|
-
'utils/spawn-tree.sh',
|
|
214
|
-
'utils/spawn-with-model.sh',
|
|
215
|
-
'utils/state-loader.sh',
|
|
216
|
-
'utils/swarm-display.sh',
|
|
217
|
-
'utils/watch-spawn-tree.sh',
|
|
218
|
-
'templates/QUEEN.md.template',
|
|
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',
|
|
219
203
|
];
|
|
220
204
|
}
|
|
221
205
|
|
|
@@ -251,7 +235,22 @@ class UpdateTransaction {
|
|
|
251
235
|
*/
|
|
252
236
|
writeJsonSync(filePath, data) {
|
|
253
237
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
254
|
-
|
|
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
|
+
}
|
|
255
254
|
}
|
|
256
255
|
|
|
257
256
|
/**
|
|
@@ -269,6 +268,40 @@ class UpdateTransaction {
|
|
|
269
268
|
}
|
|
270
269
|
}
|
|
271
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
|
+
|
|
272
305
|
/**
|
|
273
306
|
* Check if path is a git repository
|
|
274
307
|
* @returns {boolean} True if git repo
|
|
@@ -283,6 +316,54 @@ class UpdateTransaction {
|
|
|
283
316
|
}
|
|
284
317
|
}
|
|
285
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
|
+
|
|
286
367
|
/**
|
|
287
368
|
* Detect dirty repository state with detailed categorization
|
|
288
369
|
* @returns {object} Dirty state info: { isDirty, tracked, untracked, staged }
|
|
@@ -298,14 +379,25 @@ class UpdateTransaction {
|
|
|
298
379
|
encoding: 'utf8',
|
|
299
380
|
});
|
|
300
381
|
|
|
301
|
-
|
|
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);
|
|
302
386
|
const tracked = [];
|
|
303
387
|
const untracked = [];
|
|
304
388
|
const staged = [];
|
|
389
|
+
let managedCount = 0;
|
|
305
390
|
|
|
306
391
|
for (const line of lines) {
|
|
307
392
|
const status = line.slice(0, 2);
|
|
308
|
-
const
|
|
393
|
+
const rawPath = line.slice(3);
|
|
394
|
+
const filePath = this.normalizePorcelainPath(rawPath);
|
|
395
|
+
|
|
396
|
+
if (!this.isManagedUpdatePath(filePath)) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
managedCount++;
|
|
309
401
|
|
|
310
402
|
// Staged changes (in index)
|
|
311
403
|
if (status[0] !== ' ' && status[0] !== '?') {
|
|
@@ -322,7 +414,7 @@ class UpdateTransaction {
|
|
|
322
414
|
}
|
|
323
415
|
|
|
324
416
|
return {
|
|
325
|
-
isDirty:
|
|
417
|
+
isDirty: managedCount > 0,
|
|
326
418
|
tracked,
|
|
327
419
|
untracked,
|
|
328
420
|
staged,
|
|
@@ -621,10 +713,7 @@ class UpdateTransaction {
|
|
|
621
713
|
}
|
|
622
714
|
|
|
623
715
|
if (shouldCopy) {
|
|
624
|
-
|
|
625
|
-
if (relPath.endsWith('.sh')) {
|
|
626
|
-
fs.chmodSync(destPath, 0o755);
|
|
627
|
-
}
|
|
716
|
+
this.copyFileAtomic(srcPath, destPath);
|
|
628
717
|
copied++;
|
|
629
718
|
}
|
|
630
719
|
} catch (err) {
|
|
@@ -687,74 +776,59 @@ class UpdateTransaction {
|
|
|
687
776
|
}
|
|
688
777
|
|
|
689
778
|
/**
|
|
690
|
-
*
|
|
691
|
-
* @param {string}
|
|
692
|
-
* @
|
|
693
|
-
* @param {object} opts - Options
|
|
694
|
-
* @returns {object} Sync result: { copied, removed, skipped }
|
|
779
|
+
* Check if a path should be excluded from sync
|
|
780
|
+
* @param {string} relPath - Relative path
|
|
781
|
+
* @returns {boolean} True if should be excluded
|
|
695
782
|
* @private
|
|
696
783
|
*/
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
let skipped = 0;
|
|
703
|
-
|
|
704
|
-
for (const file of this.SYSTEM_FILES) {
|
|
705
|
-
const srcPath = path.join(srcDir, file);
|
|
706
|
-
const destPath = path.join(destDir, file);
|
|
707
|
-
if (fs.existsSync(srcPath)) {
|
|
708
|
-
if (!dryRun) {
|
|
709
|
-
// Compute hashes to determine if copy is needed
|
|
710
|
-
const srcHash = this.hashFileSync(srcPath);
|
|
711
|
-
const destHash = fs.existsSync(destPath) ? this.hashFileSync(destPath) : null;
|
|
712
|
-
|
|
713
|
-
if (srcHash === destHash) {
|
|
714
|
-
// Files are identical, skip copying
|
|
715
|
-
skipped++;
|
|
716
|
-
continue;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
720
|
-
fs.copyFileSync(srcPath, destPath);
|
|
721
|
-
if (file.endsWith('.sh')) {
|
|
722
|
-
fs.chmodSync(destPath, 0o755);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
copied++;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// Remove allowlisted files that no longer exist in src
|
|
730
|
-
const removed = [];
|
|
731
|
-
for (const file of this.SYSTEM_FILES) {
|
|
732
|
-
const srcPath = path.join(srcDir, file);
|
|
733
|
-
const destPath = path.join(destDir, file);
|
|
734
|
-
if (!fs.existsSync(srcPath) && fs.existsSync(destPath)) {
|
|
735
|
-
removed.push(file);
|
|
736
|
-
if (!dryRun) {
|
|
737
|
-
fs.unlinkSync(destPath);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
784
|
+
shouldExclude(relPath) {
|
|
785
|
+
const parts = relPath.split(path.sep);
|
|
786
|
+
// Check for excluded directories
|
|
787
|
+
if (parts.some(part => this.EXCLUDE_DIRS.includes(part))) {
|
|
788
|
+
return true;
|
|
740
789
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
790
|
+
// Check for excluded files (QUEEN.md protection)
|
|
791
|
+
const basename = path.basename(relPath);
|
|
792
|
+
if (this.EXCLUDE_FILES.includes(basename)) {
|
|
793
|
+
return true;
|
|
744
794
|
}
|
|
745
|
-
|
|
746
|
-
return { copied, removed, skipped };
|
|
795
|
+
return false;
|
|
747
796
|
}
|
|
748
797
|
|
|
749
798
|
/**
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* @returns {
|
|
753
|
-
* @private
|
|
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
|
|
754
802
|
*/
|
|
755
|
-
|
|
756
|
-
const
|
|
757
|
-
|
|
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;
|
|
758
832
|
}
|
|
759
833
|
|
|
760
834
|
/**
|
|
@@ -800,28 +874,30 @@ class UpdateTransaction {
|
|
|
800
874
|
const srcPath = path.join(srcDir, relPath);
|
|
801
875
|
const destPath = path.join(destDir, relPath);
|
|
802
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
|
+
|
|
803
888
|
if (!dryRun) {
|
|
804
889
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
805
890
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
const srcHash = this.hashFileSync(srcPath);
|
|
810
|
-
const destHash = this.hashFileSync(destPath);
|
|
811
|
-
if (srcHash === destHash) {
|
|
812
|
-
shouldCopy = false;
|
|
813
|
-
skipped++;
|
|
814
|
-
}
|
|
891
|
+
if (shouldCopy) {
|
|
892
|
+
this.copyFileAtomic(srcPath, destPath);
|
|
893
|
+
copied++; // FIX: Only increment when actually copied
|
|
815
894
|
}
|
|
816
|
-
|
|
895
|
+
} else {
|
|
896
|
+
// dry-run: count files that WOULD be copied
|
|
817
897
|
if (shouldCopy) {
|
|
818
|
-
|
|
819
|
-
if (relPath.endsWith('.sh')) {
|
|
820
|
-
fs.chmodSync(destPath, 0o755);
|
|
821
|
-
}
|
|
898
|
+
copied++; // Count only files that would actually be copied
|
|
822
899
|
}
|
|
823
900
|
}
|
|
824
|
-
copied++;
|
|
825
901
|
}
|
|
826
902
|
|
|
827
903
|
// Cleanup: remove files in dest that aren't in source
|
|
@@ -867,6 +943,116 @@ class UpdateTransaction {
|
|
|
867
943
|
return { copied, removed, skipped };
|
|
868
944
|
}
|
|
869
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
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Remove known stale directories and files left behind by pre-3.0.0 versions.
|
|
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
|
|
994
|
+
*
|
|
995
|
+
* Idempotent: safe to call when items do not exist.
|
|
996
|
+
*
|
|
997
|
+
* @param {string} repoPath - Absolute path to the target repository root
|
|
998
|
+
* @returns {{ cleaned: string[], failed: Array<{label: string, error: string}>, trashDir: string }}
|
|
999
|
+
*/
|
|
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
|
+
|
|
1015
|
+
const staleItems = [
|
|
1016
|
+
{
|
|
1017
|
+
path: path.join(repoPath, '.aether', 'agents'),
|
|
1018
|
+
label: '.aether/agents/ (stale duplicate)',
|
|
1019
|
+
type: 'dir',
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
path: path.join(repoPath, '.aether', 'commands'),
|
|
1023
|
+
label: '.aether/commands/ (stale duplicate)',
|
|
1024
|
+
type: 'dir',
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
path: path.join(repoPath, '.aether', 'planning.md'),
|
|
1028
|
+
label: '.aether/planning.md (phantom file)',
|
|
1029
|
+
type: 'file',
|
|
1030
|
+
},
|
|
1031
|
+
];
|
|
1032
|
+
|
|
1033
|
+
const cleaned = [];
|
|
1034
|
+
const failed = [];
|
|
1035
|
+
|
|
1036
|
+
for (const item of staleItems) {
|
|
1037
|
+
if (!fs.existsSync(item.path)) {
|
|
1038
|
+
// Already clean — idempotent skip
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
try {
|
|
1043
|
+
if (this.moveToTrash(item.path, repoPath)) {
|
|
1044
|
+
cleaned.push(item.label);
|
|
1045
|
+
} else {
|
|
1046
|
+
failed.push({ label: item.label, error: 'Failed to move to trash' });
|
|
1047
|
+
}
|
|
1048
|
+
} catch (err) {
|
|
1049
|
+
failed.push({ label: item.label, error: err.message });
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
return { cleaned, failed, trashDir: path.join(repoPath, '.aether', '.trash') };
|
|
1054
|
+
}
|
|
1055
|
+
|
|
870
1056
|
/**
|
|
871
1057
|
* Sync files from hub to repo
|
|
872
1058
|
* @param {string} sourceVersion - Version to sync from
|
|
@@ -880,14 +1066,16 @@ class UpdateTransaction {
|
|
|
880
1066
|
system: { copied: 0, removed: 0, skipped: 0 },
|
|
881
1067
|
commands: { copied: 0, removed: 0, skipped: 0 },
|
|
882
1068
|
agents: { copied: 0, removed: 0, skipped: 0 },
|
|
1069
|
+
agents_claude: { copied: 0, removed: [], skipped: 0 },
|
|
1070
|
+
rules: { copied: 0, removed: 0, skipped: 0 },
|
|
883
1071
|
errors: [],
|
|
884
1072
|
};
|
|
885
1073
|
|
|
886
1074
|
const repoAether = path.join(this.repoPath, '.aether');
|
|
887
1075
|
|
|
888
1076
|
// Sync .aether/ from hub to repo (excluding user data directories)
|
|
889
|
-
if (fs.existsSync(this.
|
|
890
|
-
results.system = this.syncAetherToRepo(this.
|
|
1077
|
+
if (fs.existsSync(this.HUB_SYSTEM_DIR)) {
|
|
1078
|
+
results.system = this.syncAetherToRepo(this.HUB_SYSTEM_DIR, repoAether, { dryRun });
|
|
891
1079
|
}
|
|
892
1080
|
|
|
893
1081
|
// Sync commands from hub
|
|
@@ -911,6 +1099,18 @@ class UpdateTransaction {
|
|
|
911
1099
|
results.agents = this.syncDirWithCleanup(this.HUB_AGENTS, repoAgents, { dryRun });
|
|
912
1100
|
}
|
|
913
1101
|
|
|
1102
|
+
// Sync claude agents from hub to .claude/agents/ant/
|
|
1103
|
+
const repoClaudeAgents = path.join(this.repoPath, '.claude', 'agents', 'ant');
|
|
1104
|
+
if (fs.existsSync(this.HUB_AGENTS_CLAUDE)) {
|
|
1105
|
+
results.agents_claude = this.syncDirWithCleanup(this.HUB_AGENTS_CLAUDE, repoClaudeAgents, { dryRun });
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Sync rules from hub to .claude/rules/
|
|
1109
|
+
const repoRules = path.join(this.repoPath, '.claude', 'rules');
|
|
1110
|
+
if (fs.existsSync(this.HUB_RULES)) {
|
|
1111
|
+
results.rules = this.syncDirWithCleanup(this.HUB_RULES, repoRules, { dryRun });
|
|
1112
|
+
}
|
|
1113
|
+
|
|
914
1114
|
this.syncResult = results;
|
|
915
1115
|
return results;
|
|
916
1116
|
}
|
|
@@ -953,10 +1153,12 @@ class UpdateTransaction {
|
|
|
953
1153
|
};
|
|
954
1154
|
|
|
955
1155
|
const repoAether = path.join(this.repoPath, '.aether');
|
|
956
|
-
verifyDir(this.
|
|
1156
|
+
verifyDir(this.HUB_SYSTEM_DIR, repoAether);
|
|
957
1157
|
verifyDir(this.HUB_COMMANDS_CLAUDE, path.join(this.repoPath, '.claude', 'commands', 'ant'));
|
|
958
1158
|
verifyDir(this.HUB_COMMANDS_OPENCODE, path.join(this.repoPath, '.opencode', 'commands', 'ant'));
|
|
959
1159
|
verifyDir(this.HUB_AGENTS, path.join(this.repoPath, '.opencode', 'agents'));
|
|
1160
|
+
verifyDir(this.HUB_AGENTS_CLAUDE, path.join(this.repoPath, '.claude', 'agents', 'ant'));
|
|
1161
|
+
verifyDir(this.HUB_RULES, path.join(this.repoPath, '.claude', 'rules'));
|
|
960
1162
|
|
|
961
1163
|
return {
|
|
962
1164
|
valid: errors.length === 0,
|
|
@@ -1002,6 +1204,8 @@ class UpdateTransaction {
|
|
|
1002
1204
|
checkDir(this.HUB_COMMANDS_CLAUDE, 'commands/claude');
|
|
1003
1205
|
checkDir(this.HUB_COMMANDS_OPENCODE, 'commands/opencode');
|
|
1004
1206
|
checkDir(this.HUB_AGENTS, 'agents');
|
|
1207
|
+
checkDir(this.HUB_AGENTS_CLAUDE, 'agents-claude');
|
|
1208
|
+
checkDir(this.HUB_RULES, 'rules');
|
|
1005
1209
|
checkDir(this.HUB_VERSION, 'version');
|
|
1006
1210
|
|
|
1007
1211
|
// Check if source files exist
|
|
@@ -1098,10 +1302,11 @@ class UpdateTransaction {
|
|
|
1098
1302
|
};
|
|
1099
1303
|
|
|
1100
1304
|
const repoAether = path.join(this.repoPath, '.aether');
|
|
1101
|
-
checkDir(this.
|
|
1305
|
+
checkDir(this.HUB_SYSTEM_DIR, repoAether);
|
|
1102
1306
|
checkDir(this.HUB_COMMANDS_CLAUDE, path.join(this.repoPath, '.claude', 'commands', 'ant'));
|
|
1103
1307
|
checkDir(this.HUB_COMMANDS_OPENCODE, path.join(this.repoPath, '.opencode', 'commands', 'ant'));
|
|
1104
1308
|
checkDir(this.HUB_AGENTS, path.join(this.repoPath, '.opencode', 'agents'));
|
|
1309
|
+
checkDir(this.HUB_RULES, path.join(this.repoPath, '.claude', 'rules'));
|
|
1105
1310
|
|
|
1106
1311
|
return {
|
|
1107
1312
|
isPartial: missing.length > 0 || corrupted.length > 0,
|
|
@@ -1260,6 +1465,14 @@ class UpdateTransaction {
|
|
|
1260
1465
|
* @returns {Promise<boolean>} True if rollback succeeded
|
|
1261
1466
|
*/
|
|
1262
1467
|
async rollback() {
|
|
1468
|
+
// Clear pending sentinel on rollback — we've cleanly returned to prior state
|
|
1469
|
+
const pendingPath = path.join(this.repoPath, '.aether', '.update-pending');
|
|
1470
|
+
try {
|
|
1471
|
+
if (fs.existsSync(pendingPath)) {
|
|
1472
|
+
fs.unlinkSync(pendingPath);
|
|
1473
|
+
}
|
|
1474
|
+
} catch { /* ignore — rollback cleanup is best-effort */ }
|
|
1475
|
+
|
|
1263
1476
|
this.state = TransactionStates.ROLLING_BACK;
|
|
1264
1477
|
this.log(' Rolling back to checkpoint...');
|
|
1265
1478
|
|
|
@@ -1270,6 +1483,23 @@ class UpdateTransaction {
|
|
|
1270
1483
|
return false;
|
|
1271
1484
|
}
|
|
1272
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
|
+
|
|
1273
1503
|
// Restore from stash if available
|
|
1274
1504
|
if (this.checkpoint.stashRef) {
|
|
1275
1505
|
try {
|
|
@@ -1342,6 +1572,13 @@ class UpdateTransaction {
|
|
|
1342
1572
|
async execute(sourceVersion, options = {}) {
|
|
1343
1573
|
const dryRun = options.dryRun || false;
|
|
1344
1574
|
|
|
1575
|
+
// Write pending sentinel BEFORE any work — marks update as in-flight
|
|
1576
|
+
const pendingPath = path.join(this.repoPath, '.aether', '.update-pending');
|
|
1577
|
+
this.writeJsonSync(pendingPath, {
|
|
1578
|
+
target_version: sourceVersion,
|
|
1579
|
+
started_at: new Date().toISOString(),
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1345
1582
|
try {
|
|
1346
1583
|
// Phase 0: Validate repo state (before any modifications)
|
|
1347
1584
|
// Check for dirty repo and provide clear recovery instructions
|
|
@@ -1368,6 +1605,9 @@ class UpdateTransaction {
|
|
|
1368
1605
|
|
|
1369
1606
|
await this.createCheckpoint();
|
|
1370
1607
|
|
|
1608
|
+
// Clean up known stale directories/files from previous versions
|
|
1609
|
+
this.cleanupResult = this.cleanupStaleAetherDirs(this.repoPath);
|
|
1610
|
+
|
|
1371
1611
|
// Phase 2: Sync (with network error handling)
|
|
1372
1612
|
this.state = TransactionStates.SYNCING;
|
|
1373
1613
|
try {
|
|
@@ -1403,15 +1643,26 @@ class UpdateTransaction {
|
|
|
1403
1643
|
this.state = TransactionStates.COMMITTING;
|
|
1404
1644
|
this.updateVersion(sourceVersion);
|
|
1405
1645
|
this.state = TransactionStates.COMMITTED;
|
|
1646
|
+
|
|
1647
|
+
// Delete pending sentinel — update is now complete
|
|
1648
|
+
try {
|
|
1649
|
+
if (fs.existsSync(pendingPath)) {
|
|
1650
|
+
fs.unlinkSync(pendingPath);
|
|
1651
|
+
}
|
|
1652
|
+
} catch (err) {
|
|
1653
|
+
this.log(` Warning: could not clear update sentinel: ${err.message}`);
|
|
1654
|
+
}
|
|
1406
1655
|
}
|
|
1407
1656
|
|
|
1408
1657
|
// Calculate totals
|
|
1409
1658
|
const filesSynced = (this.syncResult?.system?.copied || 0) +
|
|
1410
1659
|
(this.syncResult?.commands?.copied || 0) +
|
|
1411
|
-
(this.syncResult?.agents?.copied || 0)
|
|
1660
|
+
(this.syncResult?.agents?.copied || 0) +
|
|
1661
|
+
(this.syncResult?.rules?.copied || 0);
|
|
1412
1662
|
const filesRemoved = (this.syncResult?.system?.removed?.length || 0) +
|
|
1413
1663
|
(this.syncResult?.commands?.removed?.length || 0) +
|
|
1414
|
-
(this.syncResult?.agents?.removed?.length || 0)
|
|
1664
|
+
(this.syncResult?.agents?.removed?.length || 0) +
|
|
1665
|
+
(this.syncResult?.rules?.removed?.length || 0);
|
|
1415
1666
|
|
|
1416
1667
|
return {
|
|
1417
1668
|
success: true,
|
|
@@ -1420,6 +1671,7 @@ class UpdateTransaction {
|
|
|
1420
1671
|
files_synced: filesSynced,
|
|
1421
1672
|
files_removed: filesRemoved,
|
|
1422
1673
|
sync_result: this.syncResult,
|
|
1674
|
+
cleanup_result: this.cleanupResult || { cleaned: [], failed: [] },
|
|
1423
1675
|
};
|
|
1424
1676
|
|
|
1425
1677
|
} catch (error) {
|
|
@@ -1449,4 +1701,4 @@ module.exports = {
|
|
|
1449
1701
|
UpdateError,
|
|
1450
1702
|
UpdateErrorCodes,
|
|
1451
1703
|
TransactionStates,
|
|
1452
|
-
};
|
|
1704
|
+
};
|