aether-colony 3.1.4 → 3.1.15
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/.claude/commands/ant/archaeology.md +12 -0
- package/.claude/commands/ant/build.md +382 -319
- package/.claude/commands/ant/chaos.md +23 -1
- package/.claude/commands/ant/colonize.md +147 -87
- package/.claude/commands/ant/continue.md +213 -23
- package/.claude/commands/ant/council.md +22 -0
- package/.claude/commands/ant/dream.md +18 -0
- package/.claude/commands/ant/entomb.md +178 -6
- package/.claude/commands/ant/init.md +87 -13
- package/.claude/commands/ant/lay-eggs.md +45 -5
- package/.claude/commands/ant/oracle.md +82 -9
- package/.claude/commands/ant/organize.md +2 -2
- package/.claude/commands/ant/pause-colony.md +86 -28
- package/.claude/commands/ant/phase.md +26 -0
- package/.claude/commands/ant/plan.md +204 -111
- package/.claude/commands/ant/resume-colony.md +23 -1
- package/.claude/commands/ant/resume.md +159 -0
- package/.claude/commands/ant/seal.md +177 -3
- package/.claude/commands/ant/swarm.md +78 -97
- package/.claude/commands/ant/verify-castes.md +7 -7
- package/.claude/commands/ant/watch.md +17 -0
- package/.opencode/agents/aether-ambassador.md +97 -0
- package/.opencode/agents/aether-archaeologist.md +91 -0
- package/.opencode/agents/aether-architect.md +66 -0
- package/.opencode/agents/aether-auditor.md +111 -0
- package/.opencode/agents/aether-builder.md +28 -10
- package/.opencode/agents/aether-chaos.md +98 -0
- package/.opencode/agents/aether-chronicler.md +80 -0
- package/.opencode/agents/aether-gatekeeper.md +107 -0
- package/.opencode/agents/aether-guardian.md +107 -0
- package/.opencode/agents/aether-includer.md +108 -0
- package/.opencode/agents/aether-keeper.md +106 -0
- package/.opencode/agents/aether-measurer.md +119 -0
- package/.opencode/agents/aether-probe.md +91 -0
- package/.opencode/agents/aether-queen.md +72 -19
- package/.opencode/agents/aether-route-setter.md +85 -0
- package/.opencode/agents/aether-sage.md +98 -0
- package/.opencode/agents/aether-scout.md +33 -15
- package/.opencode/agents/aether-surveyor-disciplines.md +334 -0
- package/.opencode/agents/aether-surveyor-nest.md +272 -0
- package/.opencode/agents/aether-surveyor-pathogens.md +209 -0
- package/.opencode/agents/aether-surveyor-provisions.md +277 -0
- package/.opencode/agents/aether-tracker.md +91 -0
- package/.opencode/agents/aether-watcher.md +30 -12
- package/.opencode/agents/aether-weaver.md +87 -0
- package/.opencode/agents/workers.md +1034 -0
- package/.opencode/commands/ant/archaeology.md +44 -26
- package/.opencode/commands/ant/build.md +327 -295
- package/.opencode/commands/ant/chaos.md +32 -4
- package/.opencode/commands/ant/colonize.md +119 -93
- package/.opencode/commands/ant/continue.md +98 -10
- package/.opencode/commands/ant/council.md +28 -0
- package/.opencode/commands/ant/dream.md +24 -0
- package/.opencode/commands/ant/entomb.md +73 -1
- package/.opencode/commands/ant/feedback.md +8 -2
- package/.opencode/commands/ant/flag.md +9 -3
- package/.opencode/commands/ant/flags.md +8 -2
- package/.opencode/commands/ant/focus.md +8 -2
- package/.opencode/commands/ant/help.md +12 -0
- package/.opencode/commands/ant/init.md +49 -4
- package/.opencode/commands/ant/lay-eggs.md +30 -2
- package/.opencode/commands/ant/oracle.md +39 -7
- package/.opencode/commands/ant/organize.md +9 -3
- package/.opencode/commands/ant/pause-colony.md +54 -1
- package/.opencode/commands/ant/phase.md +36 -4
- package/.opencode/commands/ant/plan.md +225 -117
- package/.opencode/commands/ant/redirect.md +8 -2
- package/.opencode/commands/ant/resume-colony.md +51 -26
- package/.opencode/commands/ant/seal.md +76 -0
- package/.opencode/commands/ant/status.md +50 -20
- package/.opencode/commands/ant/swarm.md +108 -104
- package/.opencode/commands/ant/tunnels.md +107 -2
- package/CHANGELOG.md +21 -0
- package/README.md +199 -86
- package/bin/cli.js +142 -25
- package/bin/generate-commands.sh +100 -16
- package/bin/lib/caste-colors.js +5 -5
- package/bin/lib/errors.js +16 -0
- package/bin/lib/file-lock.js +279 -44
- package/bin/lib/state-sync.js +206 -23
- package/bin/lib/update-transaction.js +206 -24
- package/bin/sync-to-runtime.sh +129 -0
- package/package.json +2 -2
- package/runtime/CONTEXT.md +160 -0
- package/runtime/aether-utils.sh +1421 -55
- package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +1343 -0
- package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +2642 -0
- package/runtime/docs/PHEROMONE-INJECTION.md +240 -0
- package/runtime/docs/PHEROMONE-INTEGRATION.md +192 -0
- package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +426 -0
- package/runtime/docs/README.md +94 -0
- package/runtime/docs/VISUAL-OUTPUT-SPEC.md +219 -0
- package/runtime/docs/biological-reference.md +272 -0
- package/runtime/docs/codebase-review.md +399 -0
- package/runtime/docs/command-sync.md +164 -0
- package/runtime/docs/implementation-learnings.md +89 -0
- package/runtime/docs/known-issues.md +217 -0
- package/runtime/docs/namespace.md +148 -0
- package/runtime/docs/planning-discipline.md +159 -0
- package/runtime/lib/queen-utils.sh +729 -0
- package/runtime/model-profiles.yaml +100 -0
- package/runtime/recover.sh +136 -0
- package/runtime/templates/QUEEN.md.template +79 -0
- package/runtime/utils/atomic-write.sh +5 -5
- package/runtime/utils/chamber-utils.sh +6 -3
- package/runtime/utils/error-handler.sh +200 -0
- package/runtime/utils/queen-to-md.xsl +395 -0
- package/runtime/utils/spawn-tree.sh +428 -0
- package/runtime/utils/spawn-with-model.sh +56 -0
- package/runtime/utils/state-loader.sh +215 -0
- package/runtime/utils/swarm-display.sh +5 -5
- package/runtime/utils/watch-spawn-tree.sh +90 -22
- package/runtime/utils/xml-compose.sh +247 -0
- package/runtime/utils/xml-core.sh +186 -0
- package/runtime/utils/xml-utils.sh +2161 -0
- package/runtime/verification-loop.md +1 -1
- package/runtime/workers-new-castes.md +516 -0
- package/runtime/workers.md +20 -8
- package/.aether/visualizations/anthill-stages/brood-stable.txt +0 -26
- package/.aether/visualizations/anthill-stages/crowned-anthill.txt +0 -30
- package/.aether/visualizations/anthill-stages/first-mound.txt +0 -18
- package/.aether/visualizations/anthill-stages/open-chambers.txt +0 -24
- package/.aether/visualizations/anthill-stages/sealed-chambers.txt +0 -28
- package/.aether/visualizations/anthill-stages/ventilated-nest.txt +0 -27
package/bin/cli.js
CHANGED
|
@@ -70,11 +70,9 @@ const COMMANDS_DEST = path.join(HOME, '.claude', 'commands', 'ant');
|
|
|
70
70
|
|
|
71
71
|
// Hub paths
|
|
72
72
|
const HUB_DIR = path.join(HOME, '.aether');
|
|
73
|
-
const HUB_SYSTEM = path.join(HUB_DIR, 'system');
|
|
74
73
|
const HUB_COMMANDS_CLAUDE = path.join(HUB_DIR, 'commands', 'claude');
|
|
75
74
|
const HUB_COMMANDS_OPENCODE = path.join(HUB_DIR, 'commands', 'opencode');
|
|
76
75
|
const HUB_AGENTS = path.join(HUB_DIR, 'agents');
|
|
77
|
-
const HUB_VISUALIZATIONS = path.join(HUB_DIR, 'visualizations');
|
|
78
76
|
const HUB_REGISTRY = path.join(HUB_DIR, 'registry.json');
|
|
79
77
|
const HUB_VERSION = path.join(HUB_DIR, 'version.json');
|
|
80
78
|
|
|
@@ -836,6 +834,122 @@ function gitStashFiles(repoPath, files) {
|
|
|
836
834
|
}
|
|
837
835
|
}
|
|
838
836
|
|
|
837
|
+
// Directories to exclude from hub sync (user data, local state)
|
|
838
|
+
const HUB_EXCLUDE_DIRS = ['data', 'dreams', 'checkpoints', 'locks', 'temp'];
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Check if a path should be excluded from hub sync
|
|
842
|
+
* @param {string} relPath - Relative path from .aether/
|
|
843
|
+
* @returns {boolean} True if should be excluded
|
|
844
|
+
*/
|
|
845
|
+
function shouldExcludeFromHub(relPath) {
|
|
846
|
+
const parts = relPath.split(path.sep);
|
|
847
|
+
// Exclude if any part of the path is in the exclude list
|
|
848
|
+
return parts.some(part => HUB_EXCLUDE_DIRS.includes(part));
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Sync .aether/ directory to hub, excluding user data directories
|
|
853
|
+
* @param {string} srcDir - Source .aether/ directory
|
|
854
|
+
* @param {string} destDir - Destination hub directory
|
|
855
|
+
* @returns {object} Sync result with copied, removed, skipped counts
|
|
856
|
+
*/
|
|
857
|
+
function syncAetherToHub(srcDir, destDir) {
|
|
858
|
+
if (!fs.existsSync(srcDir)) {
|
|
859
|
+
return { copied: 0, removed: 0, skipped: 0 };
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
863
|
+
|
|
864
|
+
// Get all files in source, filtering out excluded directories
|
|
865
|
+
const srcFiles = [];
|
|
866
|
+
function collectFiles(dir, base) {
|
|
867
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
868
|
+
for (const entry of entries) {
|
|
869
|
+
if (entry.name.startsWith('.')) continue;
|
|
870
|
+
const fullPath = path.join(dir, entry.name);
|
|
871
|
+
const relPath = path.relative(base, fullPath);
|
|
872
|
+
|
|
873
|
+
if (shouldExcludeFromHub(relPath)) continue;
|
|
874
|
+
|
|
875
|
+
if (entry.isDirectory()) {
|
|
876
|
+
collectFiles(fullPath, base);
|
|
877
|
+
} else {
|
|
878
|
+
srcFiles.push(relPath);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
collectFiles(srcDir, srcDir);
|
|
883
|
+
|
|
884
|
+
// Copy files with hash comparison
|
|
885
|
+
let copied = 0;
|
|
886
|
+
let skipped = 0;
|
|
887
|
+
for (const relPath of srcFiles) {
|
|
888
|
+
const srcPath = path.join(srcDir, relPath);
|
|
889
|
+
const destPath = path.join(destDir, relPath);
|
|
890
|
+
|
|
891
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
892
|
+
|
|
893
|
+
// Hash comparison
|
|
894
|
+
let shouldCopy = true;
|
|
895
|
+
if (fs.existsSync(destPath)) {
|
|
896
|
+
const srcHash = hashFileSync(srcPath);
|
|
897
|
+
const destHash = hashFileSync(destPath);
|
|
898
|
+
if (srcHash === destHash) {
|
|
899
|
+
shouldCopy = false;
|
|
900
|
+
skipped++;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (shouldCopy) {
|
|
905
|
+
fs.copyFileSync(srcPath, destPath);
|
|
906
|
+
if (relPath.endsWith('.sh')) {
|
|
907
|
+
fs.chmodSync(destPath, 0o755);
|
|
908
|
+
}
|
|
909
|
+
copied++;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Cleanup: remove files in dest that aren't in source (and aren't excluded)
|
|
914
|
+
const destFiles = [];
|
|
915
|
+
function collectDestFiles(dir, base) {
|
|
916
|
+
if (!fs.existsSync(dir)) return;
|
|
917
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
918
|
+
for (const entry of entries) {
|
|
919
|
+
if (entry.name.startsWith('.') || entry.name === 'registry.json' || entry.name === 'version.json' || entry.name === 'manifest.json') continue;
|
|
920
|
+
const fullPath = path.join(dir, entry.name);
|
|
921
|
+
const relPath = path.relative(base, fullPath);
|
|
922
|
+
|
|
923
|
+
if (shouldExcludeFromHub(relPath)) continue;
|
|
924
|
+
|
|
925
|
+
if (entry.isDirectory()) {
|
|
926
|
+
collectDestFiles(fullPath, base);
|
|
927
|
+
} else {
|
|
928
|
+
destFiles.push(relPath);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
collectDestFiles(destDir, destDir);
|
|
933
|
+
|
|
934
|
+
const srcSet = new Set(srcFiles);
|
|
935
|
+
const removed = [];
|
|
936
|
+
for (const relPath of destFiles) {
|
|
937
|
+
if (!srcSet.has(relPath)) {
|
|
938
|
+
removed.push(relPath);
|
|
939
|
+
try {
|
|
940
|
+
fs.unlinkSync(path.join(destDir, relPath));
|
|
941
|
+
} catch (err) {
|
|
942
|
+
// Ignore cleanup errors
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Clean up empty directories
|
|
948
|
+
cleanEmptyDirs(destDir);
|
|
949
|
+
|
|
950
|
+
return { copied, removed, skipped };
|
|
951
|
+
}
|
|
952
|
+
|
|
839
953
|
function setupHub() {
|
|
840
954
|
// Create ~/.aether/ directory structure and populate from package
|
|
841
955
|
try {
|
|
@@ -848,17 +962,35 @@ function setupHub() {
|
|
|
848
962
|
log(` Warning: previous manifest is invalid, regenerating`);
|
|
849
963
|
}
|
|
850
964
|
|
|
851
|
-
// Sync runtime/ -> ~/.aether/
|
|
965
|
+
// Sync runtime/ -> ~/.aether/ (clean production files)
|
|
966
|
+
// runtime/ is the staging area - explicit allowlist via sync-to-runtime.sh
|
|
852
967
|
const runtimeSrc = path.join(PACKAGE_DIR, 'runtime');
|
|
853
968
|
if (fs.existsSync(runtimeSrc)) {
|
|
854
|
-
const result =
|
|
855
|
-
log(` Hub system: ${result.copied} files -> ${
|
|
969
|
+
const result = syncAetherToHub(runtimeSrc, HUB_DIR);
|
|
970
|
+
log(` Hub system: ${result.copied} files, ${result.skipped} unchanged -> ${HUB_DIR}`);
|
|
856
971
|
if (result.removed.length > 0) {
|
|
857
972
|
log(` Hub system: removed ${result.removed.length} stale files`);
|
|
858
973
|
for (const f of result.removed) log(` - ${f}`);
|
|
859
974
|
}
|
|
860
975
|
}
|
|
861
976
|
|
|
977
|
+
// Clean up legacy directories from old hub structure
|
|
978
|
+
const legacyDirs = [
|
|
979
|
+
path.join(HUB_DIR, 'system'),
|
|
980
|
+
path.join(HUB_DIR, '.aether'),
|
|
981
|
+
path.join(HUB_DIR, 'visualizations'),
|
|
982
|
+
];
|
|
983
|
+
for (const legacyDir of legacyDirs) {
|
|
984
|
+
if (fs.existsSync(legacyDir)) {
|
|
985
|
+
try {
|
|
986
|
+
removeDirSync(legacyDir);
|
|
987
|
+
log(` Cleaned up legacy: ${path.basename(legacyDir)}/`);
|
|
988
|
+
} catch (err) {
|
|
989
|
+
// Ignore cleanup errors
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
862
994
|
// Sync .claude/commands/ant/ -> ~/.aether/commands/claude/
|
|
863
995
|
const claudeCmdSrc = fs.existsSync(COMMANDS_SRC)
|
|
864
996
|
? COMMANDS_SRC
|
|
@@ -894,17 +1026,6 @@ function setupHub() {
|
|
|
894
1026
|
}
|
|
895
1027
|
}
|
|
896
1028
|
|
|
897
|
-
// Sync .aether/visualizations/ -> ~/.aether/visualizations/
|
|
898
|
-
const visualizationsSrc = path.join(PACKAGE_DIR, '.aether', 'visualizations');
|
|
899
|
-
if (fs.existsSync(visualizationsSrc)) {
|
|
900
|
-
const result = syncDirWithCleanup(visualizationsSrc, HUB_VISUALIZATIONS);
|
|
901
|
-
log(` Hub visualizations: ${result.copied} files -> ${HUB_VISUALIZATIONS}`);
|
|
902
|
-
if (result.removed.length > 0) {
|
|
903
|
-
log(` Hub visualizations: removed ${result.removed.length} stale files`);
|
|
904
|
-
for (const f of result.removed) log(` - ${f}`);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
1029
|
// Create/preserve registry.json
|
|
909
1030
|
if (!fs.existsSync(HUB_REGISTRY)) {
|
|
910
1031
|
writeJsonSync(HUB_REGISTRY, { schema_version: 1, repos: [] });
|
|
@@ -971,7 +1092,7 @@ async function updateRepo(repoPath, sourceVersion, opts) {
|
|
|
971
1092
|
}
|
|
972
1093
|
|
|
973
1094
|
// Use UpdateTransaction for two-phase commit with automatic rollback
|
|
974
|
-
const transaction = new UpdateTransaction(repoPath, { sourceVersion, quiet });
|
|
1095
|
+
const transaction = new UpdateTransaction(repoPath, { sourceVersion, quiet, force });
|
|
975
1096
|
|
|
976
1097
|
try {
|
|
977
1098
|
const result = await transaction.execute(sourceVersion, { dryRun });
|
|
@@ -980,18 +1101,15 @@ async function updateRepo(repoPath, sourceVersion, opts) {
|
|
|
980
1101
|
const systemCopied = result.sync_result?.system?.copied || 0;
|
|
981
1102
|
const commandsCopied = (result.sync_result?.commands?.copied || 0);
|
|
982
1103
|
const agentsCopied = result.sync_result?.agents?.copied || 0;
|
|
983
|
-
const visualizationsCopied = result.sync_result?.visualizations?.copied || 0;
|
|
984
1104
|
|
|
985
1105
|
const systemRemoved = result.sync_result?.system?.removed?.length || 0;
|
|
986
1106
|
const commandsRemoved = result.sync_result?.commands?.removed?.length || 0;
|
|
987
1107
|
const agentsRemoved = result.sync_result?.agents?.removed?.length || 0;
|
|
988
|
-
const visualizationsRemoved = result.sync_result?.visualizations?.removed?.length || 0;
|
|
989
1108
|
|
|
990
1109
|
const allRemovedFiles = [
|
|
991
1110
|
...(result.sync_result?.system?.removed || []),
|
|
992
1111
|
...(result.sync_result?.commands?.removed || []).map(f => `.claude/commands/ant/${f}`),
|
|
993
1112
|
...(result.sync_result?.agents?.removed || []).map(f => `.opencode/agents/${f}`),
|
|
994
|
-
...(result.sync_result?.visualizations?.removed || []).map(f => `.aether/visualizations/${f}`),
|
|
995
1113
|
];
|
|
996
1114
|
|
|
997
1115
|
return {
|
|
@@ -1001,8 +1119,7 @@ async function updateRepo(repoPath, sourceVersion, opts) {
|
|
|
1001
1119
|
system: systemCopied,
|
|
1002
1120
|
commands: commandsCopied,
|
|
1003
1121
|
agents: agentsCopied,
|
|
1004
|
-
|
|
1005
|
-
removed: systemRemoved + commandsRemoved + agentsRemoved + visualizationsRemoved,
|
|
1122
|
+
removed: systemRemoved + commandsRemoved + agentsRemoved,
|
|
1006
1123
|
removedFiles: allRemovedFiles,
|
|
1007
1124
|
stashCreated: !!transaction.checkpoint?.stashRef,
|
|
1008
1125
|
checkpoint_id: result.checkpoint_id,
|
|
@@ -1171,14 +1288,14 @@ program
|
|
|
1171
1288
|
console.error(` Skipping. Use --force to stash and update.`);
|
|
1172
1289
|
dirty++;
|
|
1173
1290
|
} else if (result.status === 'dry-run') {
|
|
1174
|
-
log(` Would update: ${repo.path} (${result.from} -> ${result.to}) [${result.system} system, ${result.commands} commands, ${result.agents} agents
|
|
1291
|
+
log(` Would update: ${repo.path} (${result.from} -> ${result.to}) [${result.system} system, ${result.commands} commands, ${result.agents} agents]`);
|
|
1175
1292
|
if (result.removed > 0) {
|
|
1176
1293
|
log(` Would remove ${result.removed} stale files:`);
|
|
1177
1294
|
for (const f of result.removedFiles) log(` - ${f}`);
|
|
1178
1295
|
}
|
|
1179
1296
|
updated++;
|
|
1180
1297
|
} else if (result.status === 'updated') {
|
|
1181
|
-
log(` ${c.success('Updated:')} ${repo.path} (${result.from} -> ${result.to}) [${result.system} system, ${result.commands} commands, ${result.agents} agents
|
|
1298
|
+
log(` ${c.success('Updated:')} ${repo.path} (${result.from} -> ${result.to}) [${result.system} system, ${result.commands} commands, ${result.agents} agents]`);
|
|
1182
1299
|
if (result.removed > 0) {
|
|
1183
1300
|
log(` Removed ${result.removed} stale files:`);
|
|
1184
1301
|
for (const f of result.removedFiles) log(` - ${f}`);
|
|
@@ -1254,7 +1371,7 @@ program
|
|
|
1254
1371
|
|
|
1255
1372
|
if (result.status === 'dry-run') {
|
|
1256
1373
|
console.log(`Would update: ${result.from} -> ${result.to}`);
|
|
1257
|
-
console.log(` ${result.system} system files, ${result.commands} command files, ${result.agents} agent files
|
|
1374
|
+
console.log(` ${result.system} system files, ${result.commands} command files, ${result.agents} agent files`);
|
|
1258
1375
|
if (result.removed > 0) {
|
|
1259
1376
|
console.log(` Would remove ${result.removed} stale files:`);
|
|
1260
1377
|
for (const f of result.removedFiles) console.log(` - ${f}`);
|
package/bin/generate-commands.sh
CHANGED
|
@@ -39,6 +39,28 @@ log_error() {
|
|
|
39
39
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
# Compute SHA hash with error handling
|
|
43
|
+
# Returns 0 on success, 1 on failure
|
|
44
|
+
# Echoes hash on success, error message on failure
|
|
45
|
+
compute_hash() {
|
|
46
|
+
local file="$1"
|
|
47
|
+
|
|
48
|
+
if [[ ! -r "$file" ]]; then
|
|
49
|
+
echo "NOT_READABLE"
|
|
50
|
+
return 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
local hash
|
|
54
|
+
hash=$(shasum "$file" 2>/dev/null | cut -d' ' -f1)
|
|
55
|
+
if [[ -z "$hash" ]]; then
|
|
56
|
+
echo "HASH_FAILED"
|
|
57
|
+
return 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
echo "$hash"
|
|
61
|
+
return 0
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
# Count commands in each directory
|
|
43
65
|
count_commands() {
|
|
44
66
|
local dir="$1"
|
|
@@ -49,10 +71,17 @@ count_commands() {
|
|
|
49
71
|
fi
|
|
50
72
|
}
|
|
51
73
|
|
|
52
|
-
# List command files
|
|
74
|
+
# List command files (PLAN-006 fix #13 - warn about non-.md files)
|
|
53
75
|
list_commands() {
|
|
54
76
|
local dir="$1"
|
|
55
77
|
if [[ -d "$dir" ]]; then
|
|
78
|
+
# Check for non-.md files and warn
|
|
79
|
+
local non_md_count
|
|
80
|
+
non_md_count=$(find "$dir" -type f ! -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
|
|
81
|
+
if [[ "$non_md_count" -gt 0 ]]; then
|
|
82
|
+
log_warn "$non_md_count non-.md file(s) found in $dir (ignored)"
|
|
83
|
+
fi
|
|
84
|
+
|
|
56
85
|
find "$dir" -name "*.md" -exec basename {} \; | sort
|
|
57
86
|
fi
|
|
58
87
|
}
|
|
@@ -67,6 +96,19 @@ check_sync() {
|
|
|
67
96
|
echo "Claude Code commands: $claude_count"
|
|
68
97
|
echo "OpenCode commands: $opencode_count"
|
|
69
98
|
|
|
99
|
+
# PLAN-006 fix #10 - warn about empty directories
|
|
100
|
+
if [[ "$claude_count" -eq 0 ]] && [[ "$opencode_count" -eq 0 ]]; then
|
|
101
|
+
log_warn "Both command directories are empty"
|
|
102
|
+
echo "This may indicate a misconfiguration"
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# PLAN-006 fix #11 - warn about large command counts
|
|
106
|
+
local max_commands=500
|
|
107
|
+
if [[ "$claude_count" -gt "$max_commands" ]] || [[ "$opencode_count" -gt "$max_commands" ]]; then
|
|
108
|
+
log_warn "Large number of commands ($claude_count/$opencode_count)"
|
|
109
|
+
echo "This may cause performance issues during sync checks"
|
|
110
|
+
fi
|
|
111
|
+
|
|
70
112
|
if [[ "$claude_count" != "$opencode_count" ]]; then
|
|
71
113
|
log_error "Command counts don't match!"
|
|
72
114
|
return 1
|
|
@@ -96,12 +138,16 @@ check_sync() {
|
|
|
96
138
|
check_content() {
|
|
97
139
|
log_info "Checking content-level sync (checksums)..."
|
|
98
140
|
|
|
99
|
-
local claude_files=$(list_commands "$CLAUDE_DIR")
|
|
100
141
|
local drift_count=0
|
|
142
|
+
local error_count=0
|
|
143
|
+
local match_count=0
|
|
101
144
|
local drift_files=""
|
|
145
|
+
local error_files=""
|
|
102
146
|
|
|
103
|
-
for
|
|
104
|
-
|
|
147
|
+
# Use null delimiter for safe iteration (handles filenames with spaces)
|
|
148
|
+
while IFS= read -r -d '' claude_file; do
|
|
149
|
+
local file
|
|
150
|
+
file=$(basename "$claude_file")
|
|
105
151
|
local opencode_file="$OPENCODE_DIR/$file"
|
|
106
152
|
|
|
107
153
|
# Skip if OpenCode file doesn't exist (already caught by Pass 1)
|
|
@@ -109,10 +155,23 @@ check_content() {
|
|
|
109
155
|
continue
|
|
110
156
|
fi
|
|
111
157
|
|
|
112
|
-
#
|
|
158
|
+
# Compute hashes with error handling
|
|
113
159
|
local claude_hash opencode_hash
|
|
114
|
-
claude_hash=$(
|
|
115
|
-
|
|
160
|
+
claude_hash=$(compute_hash "$claude_file")
|
|
161
|
+
if [[ $? -ne 0 ]]; then
|
|
162
|
+
log_error "Cannot hash $claude_file ($claude_hash)"
|
|
163
|
+
error_files="${error_files} ${file} (${claude_hash})\n"
|
|
164
|
+
error_count=$((error_count + 1))
|
|
165
|
+
continue
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
opencode_hash=$(compute_hash "$opencode_file")
|
|
169
|
+
if [[ $? -ne 0 ]]; then
|
|
170
|
+
log_error "Cannot hash $opencode_file ($opencode_hash)"
|
|
171
|
+
error_files="${error_files} ${file} (${opencode_hash})\n"
|
|
172
|
+
error_count=$((error_count + 1))
|
|
173
|
+
continue
|
|
174
|
+
fi
|
|
116
175
|
|
|
117
176
|
if [[ "$claude_hash" != "$opencode_hash" ]]; then
|
|
118
177
|
drift_count=$((drift_count + 1))
|
|
@@ -122,13 +181,34 @@ check_content() {
|
|
|
122
181
|
echo " Claude: $claude_hash"
|
|
123
182
|
echo " OpenCode: $opencode_hash"
|
|
124
183
|
|
|
125
|
-
#
|
|
184
|
+
# PLAN-006 fix #12 - improved diff error handling
|
|
126
185
|
echo " ---"
|
|
127
|
-
|
|
186
|
+
local diff_output
|
|
187
|
+
if diff_output=$(diff -u "$claude_file" "$opencode_file" 2>&1); then
|
|
188
|
+
# Files are same (shouldn't happen if hashes differ, but handle it)
|
|
189
|
+
echo "$diff_output" | head -20
|
|
190
|
+
else
|
|
191
|
+
local diff_exit=$?
|
|
192
|
+
if [[ "$diff_output" == *"diff:"* && "$diff_output" == *"No such file"* ]]; then
|
|
193
|
+
log_error "diff failed: $diff_output"
|
|
194
|
+
else
|
|
195
|
+
# Normal diff output (exit 1 means files differ)
|
|
196
|
+
echo "$diff_output" | head -20
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
128
199
|
echo " ---"
|
|
129
200
|
echo ""
|
|
201
|
+
else
|
|
202
|
+
match_count=$((match_count + 1))
|
|
130
203
|
fi
|
|
131
|
-
done
|
|
204
|
+
done < <(find "$CLAUDE_DIR" -name "*.md" -type f -print0 2>/dev/null | sort -z)
|
|
205
|
+
|
|
206
|
+
# Report results
|
|
207
|
+
if [[ "$error_count" -gt 0 ]]; then
|
|
208
|
+
echo ""
|
|
209
|
+
log_error "Hash errors in $error_count file(s):"
|
|
210
|
+
echo -e "$error_files"
|
|
211
|
+
fi
|
|
132
212
|
|
|
133
213
|
if [[ "$drift_count" -gt 0 ]]; then
|
|
134
214
|
echo ""
|
|
@@ -137,7 +217,11 @@ check_content() {
|
|
|
137
217
|
return 1
|
|
138
218
|
fi
|
|
139
219
|
|
|
140
|
-
|
|
220
|
+
if [[ "$error_count" -gt 0 ]]; then
|
|
221
|
+
return 1
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
log_info "All file contents match (checksums verified: $match_count files)"
|
|
141
225
|
return 0
|
|
142
226
|
}
|
|
143
227
|
|
|
@@ -145,10 +229,10 @@ check_content() {
|
|
|
145
229
|
show_diff() {
|
|
146
230
|
log_info "Comparing command sets..."
|
|
147
231
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
232
|
+
# Use null delimiter for safe iteration (handles filenames with spaces)
|
|
233
|
+
while IFS= read -r -d '' claude_file; do
|
|
234
|
+
local file
|
|
235
|
+
file=$(basename "$claude_file")
|
|
152
236
|
local opencode_file="$OPENCODE_DIR/$file"
|
|
153
237
|
|
|
154
238
|
if [[ ! -f "$opencode_file" ]]; then
|
|
@@ -163,7 +247,7 @@ show_diff() {
|
|
|
163
247
|
if [[ "$claude_size" != "$opencode_size" ]]; then
|
|
164
248
|
echo "$file: $claude_size lines (Claude) vs $opencode_size lines (OpenCode)"
|
|
165
249
|
fi
|
|
166
|
-
done
|
|
250
|
+
done < <(find "$CLAUDE_DIR" -name "*.md" -type f -print0 2>/dev/null | sort -z)
|
|
167
251
|
}
|
|
168
252
|
|
|
169
253
|
# Display help
|
package/bin/lib/caste-colors.js
CHANGED
|
@@ -17,11 +17,11 @@ const pc = require('picocolors');
|
|
|
17
17
|
|
|
18
18
|
// Caste definitions with colors and emojis (per CONTEXT.md decisions)
|
|
19
19
|
const CASTE_STYLES = {
|
|
20
|
-
builder: { color: 'blue', emoji: '
|
|
21
|
-
watcher: { color: 'green', emoji: '
|
|
22
|
-
scout: { color: 'yellow', emoji: '
|
|
23
|
-
chaos: { color: 'red', emoji: '
|
|
24
|
-
prime: { color: 'magenta',emoji: '
|
|
20
|
+
builder: { color: 'blue', emoji: '🔨🐜', ansi: '\033[34m', pc: pc.blue },
|
|
21
|
+
watcher: { color: 'green', emoji: '👁️🐜', ansi: '\033[32m', pc: pc.green },
|
|
22
|
+
scout: { color: 'yellow', emoji: '🔍🐜', ansi: '\033[33m', pc: pc.yellow },
|
|
23
|
+
chaos: { color: 'red', emoji: '🎲🐜', ansi: '\033[31m', pc: pc.red },
|
|
24
|
+
prime: { color: 'magenta',emoji: '👑🐜', ansi: '\033[35m', pc: pc.magenta }
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
// Get style for a caste (case-insensitive)
|
package/bin/lib/errors.js
CHANGED
|
@@ -179,6 +179,21 @@ class ConfigurationError extends AetherError {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* StateSchemaError - State file schema validation errors (PLAN-007 Fix 3)
|
|
184
|
+
*/
|
|
185
|
+
class StateSchemaError extends AetherError {
|
|
186
|
+
constructor(message, details = {}) {
|
|
187
|
+
super(
|
|
188
|
+
ErrorCodes.E_INVALID_STATE,
|
|
189
|
+
message,
|
|
190
|
+
details,
|
|
191
|
+
'Check state file structure and fix schema errors, or restore from backup'
|
|
192
|
+
);
|
|
193
|
+
this.name = 'StateSchemaError';
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
182
197
|
/**
|
|
183
198
|
* Map error codes to sysexits.h exit codes
|
|
184
199
|
* @param {string} code - Error code
|
|
@@ -233,6 +248,7 @@ module.exports = {
|
|
|
233
248
|
ValidationError,
|
|
234
249
|
FileSystemError,
|
|
235
250
|
ConfigurationError,
|
|
251
|
+
StateSchemaError,
|
|
236
252
|
ErrorCodes,
|
|
237
253
|
getExitCode,
|
|
238
254
|
wrapError,
|