aether-colony 1.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/CONTEXT.md +160 -0
- package/.aether/QUEEN.md +84 -0
- package/.aether/aether-utils.sh +7749 -0
- package/.aether/docs/QUEEN-SYSTEM.md +211 -0
- package/.aether/docs/README.md +68 -0
- package/.aether/docs/caste-system.md +48 -0
- package/.aether/docs/disciplines/DISCIPLINES.md +93 -0
- package/.aether/docs/disciplines/coding-standards.md +197 -0
- package/.aether/docs/disciplines/debugging.md +207 -0
- package/.aether/docs/disciplines/learning.md +254 -0
- package/.aether/docs/disciplines/tdd.md +257 -0
- package/.aether/docs/disciplines/verification-loop.md +167 -0
- package/.aether/docs/disciplines/verification.md +116 -0
- package/.aether/docs/error-codes.md +268 -0
- package/.aether/docs/known-issues.md +233 -0
- package/.aether/docs/pheromones.md +205 -0
- package/.aether/docs/queen-commands.md +97 -0
- package/.aether/exchange/colony-registry.xml +11 -0
- package/.aether/exchange/pheromone-xml.sh +575 -0
- package/.aether/exchange/pheromones.xml +87 -0
- package/.aether/exchange/queen-wisdom.xml +14 -0
- package/.aether/exchange/registry-xml.sh +273 -0
- package/.aether/exchange/wisdom-xml.sh +319 -0
- package/.aether/midden/approach-changes.md +5 -0
- package/.aether/midden/build-failures.md +5 -0
- package/.aether/midden/test-failures.md +5 -0
- package/.aether/model-profiles.yaml +100 -0
- package/.aether/rules/aether-colony.md +134 -0
- package/.aether/schemas/aether-types.xsd +255 -0
- package/.aether/schemas/colony-registry.xsd +309 -0
- package/.aether/schemas/example-prompt-builder.xml +234 -0
- package/.aether/schemas/pheromone.xsd +163 -0
- package/.aether/schemas/prompt.xsd +416 -0
- package/.aether/schemas/queen-wisdom.xsd +325 -0
- package/.aether/schemas/worker-priming.xsd +276 -0
- package/.aether/templates/QUEEN.md.template +79 -0
- package/.aether/templates/colony-state-reset.jq.template +22 -0
- package/.aether/templates/colony-state.template.json +35 -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 +7 -0
- package/.aether/templates/pheromones.template.json +6 -0
- package/.aether/templates/session.template.json +9 -0
- package/.aether/utils/atomic-write.sh +219 -0
- package/.aether/utils/chamber-compare.sh +193 -0
- package/.aether/utils/chamber-utils.sh +297 -0
- package/.aether/utils/colorize-log.sh +132 -0
- package/.aether/utils/error-handler.sh +212 -0
- package/.aether/utils/file-lock.sh +158 -0
- package/.aether/utils/queen-to-md.xsl +395 -0
- package/.aether/utils/semantic-cli.sh +413 -0
- package/.aether/utils/spawn-tree.sh +428 -0
- package/.aether/utils/spawn-with-model.sh +56 -0
- package/.aether/utils/state-loader.sh +215 -0
- package/.aether/utils/swarm-display.sh +268 -0
- package/.aether/utils/watch-spawn-tree.sh +253 -0
- package/.aether/utils/xml-compose.sh +253 -0
- package/.aether/utils/xml-convert.sh +273 -0
- package/.aether/utils/xml-core.sh +186 -0
- package/.aether/utils/xml-query.sh +201 -0
- package/.aether/utils/xml-utils.sh +110 -0
- package/.aether/workers.md +765 -0
- package/.claude/agents/ant/aether-ambassador.md +264 -0
- package/.claude/agents/ant/aether-archaeologist.md +322 -0
- package/.claude/agents/ant/aether-auditor.md +266 -0
- package/.claude/agents/ant/aether-builder.md +187 -0
- package/.claude/agents/ant/aether-chaos.md +268 -0
- package/.claude/agents/ant/aether-chronicler.md +304 -0
- package/.claude/agents/ant/aether-gatekeeper.md +325 -0
- package/.claude/agents/ant/aether-includer.md +373 -0
- package/.claude/agents/ant/aether-keeper.md +271 -0
- package/.claude/agents/ant/aether-measurer.md +317 -0
- package/.claude/agents/ant/aether-probe.md +210 -0
- package/.claude/agents/ant/aether-queen.md +325 -0
- package/.claude/agents/ant/aether-route-setter.md +173 -0
- package/.claude/agents/ant/aether-sage.md +353 -0
- package/.claude/agents/ant/aether-scout.md +142 -0
- package/.claude/agents/ant/aether-surveyor-disciplines.md +416 -0
- package/.claude/agents/ant/aether-surveyor-nest.md +354 -0
- package/.claude/agents/ant/aether-surveyor-pathogens.md +288 -0
- package/.claude/agents/ant/aether-surveyor-provisions.md +359 -0
- package/.claude/agents/ant/aether-tracker.md +265 -0
- package/.claude/agents/ant/aether-watcher.md +244 -0
- package/.claude/agents/ant/aether-weaver.md +247 -0
- package/.claude/commands/ant/archaeology.md +341 -0
- package/.claude/commands/ant/build.md +1160 -0
- package/.claude/commands/ant/chaos.md +349 -0
- package/.claude/commands/ant/colonize.md +270 -0
- package/.claude/commands/ant/continue.md +1070 -0
- package/.claude/commands/ant/council.md +309 -0
- package/.claude/commands/ant/dream.md +265 -0
- package/.claude/commands/ant/entomb.md +487 -0
- package/.claude/commands/ant/feedback.md +78 -0
- package/.claude/commands/ant/flag.md +139 -0
- package/.claude/commands/ant/flags.md +155 -0
- package/.claude/commands/ant/focus.md +58 -0
- package/.claude/commands/ant/help.md +122 -0
- package/.claude/commands/ant/history.md +137 -0
- package/.claude/commands/ant/init.md +409 -0
- package/.claude/commands/ant/interpret.md +267 -0
- package/.claude/commands/ant/lay-eggs.md +201 -0
- package/.claude/commands/ant/maturity.md +102 -0
- package/.claude/commands/ant/memory-details.md +77 -0
- package/.claude/commands/ant/migrate-state.md +165 -0
- package/.claude/commands/ant/oracle.md +387 -0
- package/.claude/commands/ant/organize.md +227 -0
- package/.claude/commands/ant/pause-colony.md +247 -0
- package/.claude/commands/ant/phase.md +126 -0
- package/.claude/commands/ant/plan.md +544 -0
- package/.claude/commands/ant/redirect.md +58 -0
- package/.claude/commands/ant/resume-colony.md +182 -0
- package/.claude/commands/ant/resume.md +363 -0
- package/.claude/commands/ant/seal.md +306 -0
- package/.claude/commands/ant/status.md +272 -0
- package/.claude/commands/ant/swarm.md +361 -0
- package/.claude/commands/ant/tunnels.md +425 -0
- package/.claude/commands/ant/update.md +209 -0
- package/.claude/commands/ant/verify-castes.md +95 -0
- package/.claude/commands/ant/watch.md +238 -0
- package/.opencode/agents/aether-ambassador.md +140 -0
- package/.opencode/agents/aether-archaeologist.md +108 -0
- package/.opencode/agents/aether-auditor.md +144 -0
- package/.opencode/agents/aether-builder.md +184 -0
- package/.opencode/agents/aether-chaos.md +115 -0
- package/.opencode/agents/aether-chronicler.md +122 -0
- package/.opencode/agents/aether-gatekeeper.md +116 -0
- package/.opencode/agents/aether-includer.md +117 -0
- package/.opencode/agents/aether-keeper.md +177 -0
- package/.opencode/agents/aether-measurer.md +128 -0
- package/.opencode/agents/aether-probe.md +133 -0
- package/.opencode/agents/aether-queen.md +286 -0
- package/.opencode/agents/aether-route-setter.md +130 -0
- package/.opencode/agents/aether-sage.md +106 -0
- package/.opencode/agents/aether-scout.md +101 -0
- package/.opencode/agents/aether-surveyor-disciplines.md +386 -0
- package/.opencode/agents/aether-surveyor-nest.md +324 -0
- package/.opencode/agents/aether-surveyor-pathogens.md +259 -0
- package/.opencode/agents/aether-surveyor-provisions.md +329 -0
- package/.opencode/agents/aether-tracker.md +137 -0
- package/.opencode/agents/aether-watcher.md +174 -0
- package/.opencode/agents/aether-weaver.md +130 -0
- package/.opencode/commands/ant/archaeology.md +338 -0
- package/.opencode/commands/ant/build.md +1200 -0
- package/.opencode/commands/ant/chaos.md +346 -0
- package/.opencode/commands/ant/colonize.md +202 -0
- package/.opencode/commands/ant/continue.md +938 -0
- package/.opencode/commands/ant/council.md +305 -0
- package/.opencode/commands/ant/dream.md +262 -0
- package/.opencode/commands/ant/entomb.md +367 -0
- package/.opencode/commands/ant/feedback.md +80 -0
- package/.opencode/commands/ant/flag.md +137 -0
- package/.opencode/commands/ant/flags.md +153 -0
- package/.opencode/commands/ant/focus.md +56 -0
- package/.opencode/commands/ant/help.md +124 -0
- package/.opencode/commands/ant/history.md +127 -0
- package/.opencode/commands/ant/init.md +337 -0
- package/.opencode/commands/ant/interpret.md +256 -0
- package/.opencode/commands/ant/lay-eggs.md +141 -0
- package/.opencode/commands/ant/maturity.md +92 -0
- package/.opencode/commands/ant/memory-details.md +77 -0
- package/.opencode/commands/ant/migrate-state.md +153 -0
- package/.opencode/commands/ant/oracle.md +338 -0
- package/.opencode/commands/ant/organize.md +224 -0
- package/.opencode/commands/ant/pause-colony.md +220 -0
- package/.opencode/commands/ant/phase.md +123 -0
- package/.opencode/commands/ant/plan.md +531 -0
- package/.opencode/commands/ant/redirect.md +67 -0
- package/.opencode/commands/ant/resume-colony.md +178 -0
- package/.opencode/commands/ant/resume.md +363 -0
- package/.opencode/commands/ant/seal.md +247 -0
- package/.opencode/commands/ant/status.md +272 -0
- package/.opencode/commands/ant/swarm.md +357 -0
- package/.opencode/commands/ant/tunnels.md +406 -0
- package/.opencode/commands/ant/update.md +191 -0
- package/.opencode/commands/ant/verify-castes.md +85 -0
- package/.opencode/commands/ant/watch.md +220 -0
- package/.opencode/opencode.json +3 -0
- package/CHANGELOG.md +325 -0
- package/DISCLAIMER.md +74 -0
- package/LICENSE +21 -0
- package/README.md +258 -0
- package/bin/cli.js +2436 -0
- package/bin/generate-commands.sh +291 -0
- package/bin/lib/caste-colors.js +57 -0
- package/bin/lib/colors.js +76 -0
- package/bin/lib/errors.js +255 -0
- package/bin/lib/event-types.js +190 -0
- package/bin/lib/file-lock.js +695 -0
- package/bin/lib/init.js +454 -0
- package/bin/lib/logger.js +242 -0
- package/bin/lib/model-profiles.js +445 -0
- package/bin/lib/model-verify.js +288 -0
- package/bin/lib/nestmate-loader.js +130 -0
- package/bin/lib/proxy-health.js +253 -0
- package/bin/lib/spawn-logger.js +266 -0
- package/bin/lib/state-guard.js +602 -0
- package/bin/lib/state-sync.js +516 -0
- package/bin/lib/telemetry.js +441 -0
- package/bin/lib/update-transaction.js +1454 -0
- package/bin/npx-install.js +178 -0
- package/bin/sync-to-runtime.sh +6 -0
- package/bin/validate-package.sh +88 -0
- package/package.json +70 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Telemetry Module
|
|
4
|
+
*
|
|
5
|
+
* Tracks model performance and routing decisions for data-driven model selection.
|
|
6
|
+
* Records every spawn with model, caste, task, and routing source.
|
|
7
|
+
* Tracks success/failure rates per model-caste combination.
|
|
8
|
+
* Rotates at 1000 routing decisions to prevent unbounded growth.
|
|
9
|
+
* Uses atomic writes (temp file + rename) for data integrity.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const TELEMETRY_VERSION = '1.0';
|
|
16
|
+
const MAX_ROUTING_DECISIONS = 1000;
|
|
17
|
+
const DEFAULT_TELEMETRY_FILE = 'telemetry.json';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the telemetry file path
|
|
21
|
+
* @param {string} repoPath - Repository root path
|
|
22
|
+
* @returns {string} Full path to telemetry.json
|
|
23
|
+
*/
|
|
24
|
+
function getTelemetryPath(repoPath) {
|
|
25
|
+
return path.join(repoPath, '.aether', 'data', DEFAULT_TELEMETRY_FILE);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Load telemetry data from file
|
|
30
|
+
* @param {string} repoPath - Repository root path
|
|
31
|
+
* @returns {Object} Parsed telemetry data or default structure
|
|
32
|
+
*/
|
|
33
|
+
function loadTelemetry(repoPath) {
|
|
34
|
+
const telemetryPath = getTelemetryPath(repoPath);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(telemetryPath)) {
|
|
38
|
+
return createDefaultTelemetry();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const content = fs.readFileSync(telemetryPath, 'utf8');
|
|
42
|
+
const data = JSON.parse(content);
|
|
43
|
+
|
|
44
|
+
// Validate structure
|
|
45
|
+
if (!data.version || !data.models || !Array.isArray(data.routing_decisions)) {
|
|
46
|
+
return createDefaultTelemetry();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return data;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Return default on any error (corrupted file, permission issues, etc.)
|
|
52
|
+
return createDefaultTelemetry();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create default telemetry structure
|
|
58
|
+
* @returns {Object} Default telemetry object
|
|
59
|
+
*/
|
|
60
|
+
function createDefaultTelemetry() {
|
|
61
|
+
return {
|
|
62
|
+
version: TELEMETRY_VERSION,
|
|
63
|
+
last_updated: new Date().toISOString(),
|
|
64
|
+
models: {},
|
|
65
|
+
routing_decisions: []
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Save telemetry data atomically
|
|
71
|
+
* @param {string} repoPath - Repository root path
|
|
72
|
+
* @param {Object} data - Telemetry data to save
|
|
73
|
+
* @returns {boolean} True if saved successfully
|
|
74
|
+
*/
|
|
75
|
+
function saveTelemetry(repoPath, data) {
|
|
76
|
+
const telemetryPath = getTelemetryPath(repoPath);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Ensure directory exists
|
|
80
|
+
const dataDir = path.dirname(telemetryPath);
|
|
81
|
+
if (!fs.existsSync(dataDir)) {
|
|
82
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update timestamp
|
|
86
|
+
data.last_updated = new Date().toISOString();
|
|
87
|
+
|
|
88
|
+
// Atomic write: write to temp file, then rename
|
|
89
|
+
const tempPath = `${telemetryPath}.tmp`;
|
|
90
|
+
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf8');
|
|
91
|
+
fs.renameSync(tempPath, telemetryPath);
|
|
92
|
+
|
|
93
|
+
return true;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// Silent fail - don't cascade errors from telemetry
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Record spawn telemetry
|
|
102
|
+
* @param {string} repoPath - Repository root path
|
|
103
|
+
* @param {Object} spawnInfo - Spawn details
|
|
104
|
+
* @param {string} spawnInfo.task - Task description
|
|
105
|
+
* @param {string} spawnInfo.caste - Worker caste (e.g., "builder")
|
|
106
|
+
* @param {string} spawnInfo.model - Model used (e.g., "kimi-k2.5")
|
|
107
|
+
* @param {string} spawnInfo.source - Routing source (e.g., "caste-default", "task-based")
|
|
108
|
+
* @param {string} [spawnInfo.timestamp] - Optional timestamp (defaults to now)
|
|
109
|
+
* @returns {Object} Result with success flag and decision_id
|
|
110
|
+
*/
|
|
111
|
+
function recordSpawnTelemetry(repoPath, { task, caste, model, source, timestamp }) {
|
|
112
|
+
try {
|
|
113
|
+
const data = loadTelemetry(repoPath);
|
|
114
|
+
const decisionTimestamp = timestamp || new Date().toISOString();
|
|
115
|
+
|
|
116
|
+
// Initialize model stats if not exists
|
|
117
|
+
if (!data.models[model]) {
|
|
118
|
+
data.models[model] = {
|
|
119
|
+
total_spawns: 0,
|
|
120
|
+
successful_completions: 0,
|
|
121
|
+
failed_completions: 0,
|
|
122
|
+
blocked: 0,
|
|
123
|
+
by_caste: {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const modelStats = data.models[model];
|
|
128
|
+
|
|
129
|
+
// Increment total spawns
|
|
130
|
+
modelStats.total_spawns++;
|
|
131
|
+
|
|
132
|
+
// Initialize caste stats if not exists
|
|
133
|
+
if (!modelStats.by_caste[caste]) {
|
|
134
|
+
modelStats.by_caste[caste] = {
|
|
135
|
+
spawns: 0,
|
|
136
|
+
success: 0,
|
|
137
|
+
failures: 0,
|
|
138
|
+
blocked: 0
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Increment caste spawns
|
|
143
|
+
modelStats.by_caste[caste].spawns++;
|
|
144
|
+
|
|
145
|
+
// Create routing decision record with activity tracking fields
|
|
146
|
+
const decision = {
|
|
147
|
+
timestamp: decisionTimestamp,
|
|
148
|
+
task: task || 'unknown',
|
|
149
|
+
caste: caste || 'unknown',
|
|
150
|
+
selected_model: model || 'default',
|
|
151
|
+
source: source || 'unknown',
|
|
152
|
+
tools: { read: 0, grep: 0, edit: 0, bash: 0 },
|
|
153
|
+
tokens: 0,
|
|
154
|
+
started_at: decisionTimestamp
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Append to routing decisions
|
|
158
|
+
data.routing_decisions.push(decision);
|
|
159
|
+
|
|
160
|
+
// Rotate if exceeds max
|
|
161
|
+
if (data.routing_decisions.length > MAX_ROUTING_DECISIONS) {
|
|
162
|
+
data.routing_decisions = data.routing_decisions.slice(-MAX_ROUTING_DECISIONS);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Save atomically
|
|
166
|
+
const saved = saveTelemetry(repoPath, data);
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
success: saved,
|
|
170
|
+
decision_id: decisionTimestamp
|
|
171
|
+
};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
decision_id: null,
|
|
176
|
+
error: error.message
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Update spawn outcome
|
|
183
|
+
* @param {string} repoPath - Repository root path
|
|
184
|
+
* @param {string} spawnId - Spawn identifier (timestamp from recordSpawnTelemetry)
|
|
185
|
+
* @param {string} outcome - Outcome: 'completed' | 'failed' | 'blocked'
|
|
186
|
+
* @returns {boolean} True if updated successfully
|
|
187
|
+
*/
|
|
188
|
+
function updateSpawnOutcome(repoPath, spawnId, outcome) {
|
|
189
|
+
try {
|
|
190
|
+
const data = loadTelemetry(repoPath);
|
|
191
|
+
|
|
192
|
+
// Find the routing decision by timestamp
|
|
193
|
+
const decision = data.routing_decisions.find(d => d.timestamp === spawnId);
|
|
194
|
+
|
|
195
|
+
if (!decision) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { selected_model: model, caste } = decision;
|
|
200
|
+
|
|
201
|
+
// Ensure model exists
|
|
202
|
+
if (!data.models[model]) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const modelStats = data.models[model];
|
|
207
|
+
|
|
208
|
+
// Update model-level counters
|
|
209
|
+
switch (outcome) {
|
|
210
|
+
case 'completed':
|
|
211
|
+
modelStats.successful_completions++;
|
|
212
|
+
break;
|
|
213
|
+
case 'failed':
|
|
214
|
+
modelStats.failed_completions++;
|
|
215
|
+
break;
|
|
216
|
+
case 'blocked':
|
|
217
|
+
modelStats.blocked++;
|
|
218
|
+
break;
|
|
219
|
+
default:
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Update caste-level counters
|
|
224
|
+
if (modelStats.by_caste[caste]) {
|
|
225
|
+
switch (outcome) {
|
|
226
|
+
case 'completed':
|
|
227
|
+
modelStats.by_caste[caste].success++;
|
|
228
|
+
break;
|
|
229
|
+
case 'failed':
|
|
230
|
+
modelStats.by_caste[caste].failures++;
|
|
231
|
+
break;
|
|
232
|
+
case 'blocked':
|
|
233
|
+
modelStats.by_caste[caste].blocked++;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Save atomically
|
|
239
|
+
return saveTelemetry(repoPath, data);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get telemetry summary
|
|
247
|
+
* @param {string} repoPath - Repository root path
|
|
248
|
+
* @returns {Object} Summary of telemetry data
|
|
249
|
+
*/
|
|
250
|
+
function getTelemetrySummary(repoPath) {
|
|
251
|
+
const data = loadTelemetry(repoPath);
|
|
252
|
+
|
|
253
|
+
const totalSpawns = Object.values(data.models).reduce(
|
|
254
|
+
(sum, model) => sum + (model.total_spawns || 0),
|
|
255
|
+
0
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const models = {};
|
|
259
|
+
for (const [modelName, stats] of Object.entries(data.models)) {
|
|
260
|
+
const successRate = stats.total_spawns > 0
|
|
261
|
+
? (stats.successful_completions / stats.total_spawns)
|
|
262
|
+
: 0;
|
|
263
|
+
|
|
264
|
+
models[modelName] = {
|
|
265
|
+
total_spawns: stats.total_spawns,
|
|
266
|
+
success_rate: Math.round(successRate * 100) / 100,
|
|
267
|
+
by_caste: stats.by_caste || {}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Get last 10 routing decisions
|
|
272
|
+
const recentDecisions = data.routing_decisions.slice(-10);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
total_spawns: totalSpawns,
|
|
276
|
+
total_models: Object.keys(data.models).length,
|
|
277
|
+
models,
|
|
278
|
+
recent_decisions: recentDecisions
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get detailed performance for a specific model
|
|
284
|
+
* @param {string} repoPath - Repository root path
|
|
285
|
+
* @param {string} model - Model name
|
|
286
|
+
* @returns {Object|null} Model performance data or null if not found
|
|
287
|
+
*/
|
|
288
|
+
function getModelPerformance(repoPath, model) {
|
|
289
|
+
const data = loadTelemetry(repoPath);
|
|
290
|
+
|
|
291
|
+
if (!data.models[model]) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const stats = data.models[model];
|
|
296
|
+
const successRate = stats.total_spawns > 0
|
|
297
|
+
? (stats.successful_completions / stats.total_spawns)
|
|
298
|
+
: 0;
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
model,
|
|
302
|
+
total_spawns: stats.total_spawns,
|
|
303
|
+
successful_completions: stats.successful_completions || 0,
|
|
304
|
+
failed_completions: stats.failed_completions || 0,
|
|
305
|
+
blocked: stats.blocked || 0,
|
|
306
|
+
success_rate: Math.round(successRate * 100) / 100,
|
|
307
|
+
by_caste: stats.by_caste || {}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get routing statistics with optional filtering
|
|
313
|
+
* @param {string} repoPath - Repository root path
|
|
314
|
+
* @param {Object} options - Filter options
|
|
315
|
+
* @param {number} [options.days] - Filter to last N days
|
|
316
|
+
* @param {string} [options.caste] - Filter by caste
|
|
317
|
+
* @returns {Object} Routing statistics
|
|
318
|
+
*/
|
|
319
|
+
function getRoutingStats(repoPath, options = {}) {
|
|
320
|
+
const data = loadTelemetry(repoPath);
|
|
321
|
+
const { days, caste } = options;
|
|
322
|
+
|
|
323
|
+
let decisions = [...data.routing_decisions];
|
|
324
|
+
|
|
325
|
+
// Filter by days
|
|
326
|
+
if (days && days > 0) {
|
|
327
|
+
const cutoffDate = new Date();
|
|
328
|
+
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
329
|
+
|
|
330
|
+
decisions = decisions.filter(d => new Date(d.timestamp) >= cutoffDate);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Filter by caste
|
|
334
|
+
if (caste) {
|
|
335
|
+
decisions = decisions.filter(d => d.caste === caste);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Calculate stats
|
|
339
|
+
const bySource = {};
|
|
340
|
+
const byModel = {};
|
|
341
|
+
|
|
342
|
+
for (const decision of decisions) {
|
|
343
|
+
// Count by source
|
|
344
|
+
bySource[decision.source] = (bySource[decision.source] || 0) + 1;
|
|
345
|
+
|
|
346
|
+
// Count by model
|
|
347
|
+
byModel[decision.selected_model] = (byModel[decision.selected_model] || 0) + 1;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
total_decisions: decisions.length,
|
|
352
|
+
by_source: bySource,
|
|
353
|
+
by_model: byModel,
|
|
354
|
+
date_range: decisions.length > 0 ? {
|
|
355
|
+
earliest: decisions[0].timestamp,
|
|
356
|
+
latest: decisions[decisions.length - 1].timestamp
|
|
357
|
+
} : null
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Update tool usage counter for a spawn
|
|
363
|
+
* @param {string} repoPath - Repository root path
|
|
364
|
+
* @param {string} spawnId - Spawn identifier (timestamp from recordSpawnTelemetry)
|
|
365
|
+
* @param {string} toolType - Tool type: 'read', 'grep', 'edit', 'bash'
|
|
366
|
+
* @param {number} [count=1] - Number to increment by
|
|
367
|
+
* @returns {boolean} True if updated successfully
|
|
368
|
+
*/
|
|
369
|
+
function updateToolUsage(repoPath, spawnId, toolType, count = 1) {
|
|
370
|
+
try {
|
|
371
|
+
const data = loadTelemetry(repoPath);
|
|
372
|
+
|
|
373
|
+
// Find the routing decision by timestamp
|
|
374
|
+
const decision = data.routing_decisions.find(d => d.timestamp === spawnId);
|
|
375
|
+
|
|
376
|
+
if (!decision) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Initialize tools object if not exists
|
|
381
|
+
if (!decision.tools) {
|
|
382
|
+
decision.tools = { read: 0, grep: 0, edit: 0, bash: 0 };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Validate tool type
|
|
386
|
+
const validTools = ['read', 'grep', 'edit', 'bash'];
|
|
387
|
+
if (!validTools.includes(toolType)) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Increment the specified tool counter
|
|
392
|
+
decision.tools[toolType] += count;
|
|
393
|
+
|
|
394
|
+
// Save atomically
|
|
395
|
+
return saveTelemetry(repoPath, data);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Update token usage (trophallaxis metrics) for a spawn
|
|
403
|
+
* @param {string} repoPath - Repository root path
|
|
404
|
+
* @param {string} spawnId - Spawn identifier (timestamp from recordSpawnTelemetry)
|
|
405
|
+
* @param {number} tokens - Number of tokens to add (cumulative)
|
|
406
|
+
* @returns {boolean} True if updated successfully
|
|
407
|
+
*/
|
|
408
|
+
function updateTokenUsage(repoPath, spawnId, tokens) {
|
|
409
|
+
try {
|
|
410
|
+
const data = loadTelemetry(repoPath);
|
|
411
|
+
|
|
412
|
+
// Find the routing decision by timestamp
|
|
413
|
+
const decision = data.routing_decisions.find(d => d.timestamp === spawnId);
|
|
414
|
+
|
|
415
|
+
if (!decision) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Add tokens to existing count (cumulative)
|
|
420
|
+
decision.tokens = (decision.tokens || 0) + tokens;
|
|
421
|
+
|
|
422
|
+
// Save atomically
|
|
423
|
+
return saveTelemetry(repoPath, data);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
module.exports = {
|
|
430
|
+
recordSpawnTelemetry,
|
|
431
|
+
updateSpawnOutcome,
|
|
432
|
+
updateToolUsage,
|
|
433
|
+
updateTokenUsage,
|
|
434
|
+
getTelemetrySummary,
|
|
435
|
+
getModelPerformance,
|
|
436
|
+
getRoutingStats,
|
|
437
|
+
loadTelemetry,
|
|
438
|
+
saveTelemetry,
|
|
439
|
+
TELEMETRY_VERSION,
|
|
440
|
+
MAX_ROUTING_DECISIONS
|
|
441
|
+
};
|