opencode-swarm-plugin 0.44.0 → 0.44.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/swarm.serve.test.ts +6 -4
- package/bin/swarm.ts +16 -10
- package/dist/compaction-prompt-scoring.js +139 -0
- package/dist/eval-capture.js +12811 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.js +7644 -62599
- package/dist/plugin.js +23766 -78721
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-review.d.ts.map +1 -1
- package/package.json +17 -5
- package/.changeset/swarm-insights-data-layer.md +0 -63
- package/.hive/analysis/eval-failure-analysis-2025-12-25.md +0 -331
- package/.hive/analysis/session-data-quality-audit.md +0 -320
- package/.hive/eval-results.json +0 -483
- package/.hive/issues.jsonl +0 -138
- package/.hive/memories.jsonl +0 -729
- package/.opencode/eval-history.jsonl +0 -327
- package/.turbo/turbo-build.log +0 -9
- package/CHANGELOG.md +0 -2286
- package/SCORER-ANALYSIS.md +0 -598
- package/docs/analysis/subagent-coordination-patterns.md +0 -902
- package/docs/analysis-socratic-planner-pattern.md +0 -504
- package/docs/planning/ADR-001-monorepo-structure.md +0 -171
- package/docs/planning/ADR-002-package-extraction.md +0 -393
- package/docs/planning/ADR-003-performance-improvements.md +0 -451
- package/docs/planning/ADR-004-message-queue-features.md +0 -187
- package/docs/planning/ADR-005-devtools-observability.md +0 -202
- package/docs/planning/ADR-007-swarm-enhancements-worktree-review.md +0 -168
- package/docs/planning/ADR-008-worker-handoff-protocol.md +0 -293
- package/docs/planning/ADR-009-oh-my-opencode-patterns.md +0 -353
- package/docs/planning/ADR-010-cass-inhousing.md +0 -1215
- package/docs/planning/ROADMAP.md +0 -368
- package/docs/semantic-memory-cli-syntax.md +0 -123
- package/docs/swarm-mail-architecture.md +0 -1147
- package/docs/testing/context-recovery-test.md +0 -470
- package/evals/ARCHITECTURE.md +0 -1189
- package/evals/README.md +0 -768
- package/evals/compaction-prompt.eval.ts +0 -149
- package/evals/compaction-resumption.eval.ts +0 -289
- package/evals/coordinator-behavior.eval.ts +0 -307
- package/evals/coordinator-session.eval.ts +0 -154
- package/evals/evalite.config.ts.bak +0 -15
- package/evals/example.eval.ts +0 -31
- package/evals/fixtures/cass-baseline.ts +0 -217
- package/evals/fixtures/compaction-cases.ts +0 -350
- package/evals/fixtures/compaction-prompt-cases.ts +0 -311
- package/evals/fixtures/coordinator-sessions.ts +0 -328
- package/evals/fixtures/decomposition-cases.ts +0 -105
- package/evals/lib/compaction-loader.test.ts +0 -248
- package/evals/lib/compaction-loader.ts +0 -320
- package/evals/lib/data-loader.evalite-test.ts +0 -289
- package/evals/lib/data-loader.test.ts +0 -345
- package/evals/lib/data-loader.ts +0 -281
- package/evals/lib/llm.ts +0 -115
- package/evals/scorers/compaction-prompt-scorers.ts +0 -145
- package/evals/scorers/compaction-scorers.ts +0 -305
- package/evals/scorers/coordinator-discipline.evalite-test.ts +0 -539
- package/evals/scorers/coordinator-discipline.ts +0 -325
- package/evals/scorers/index.test.ts +0 -146
- package/evals/scorers/index.ts +0 -328
- package/evals/scorers/outcome-scorers.evalite-test.ts +0 -27
- package/evals/scorers/outcome-scorers.ts +0 -349
- package/evals/swarm-decomposition.eval.ts +0 -121
- package/examples/commands/swarm.md +0 -745
- package/examples/plugin-wrapper-template.ts +0 -2515
- package/examples/skills/hive-workflow/SKILL.md +0 -212
- package/examples/skills/skill-creator/SKILL.md +0 -223
- package/examples/skills/swarm-coordination/SKILL.md +0 -292
- package/global-skills/cli-builder/SKILL.md +0 -344
- package/global-skills/cli-builder/references/advanced-patterns.md +0 -244
- package/global-skills/learning-systems/SKILL.md +0 -644
- package/global-skills/skill-creator/LICENSE.txt +0 -202
- package/global-skills/skill-creator/SKILL.md +0 -352
- package/global-skills/skill-creator/references/output-patterns.md +0 -82
- package/global-skills/skill-creator/references/workflows.md +0 -28
- package/global-skills/swarm-coordination/SKILL.md +0 -995
- package/global-skills/swarm-coordination/references/coordinator-patterns.md +0 -235
- package/global-skills/swarm-coordination/references/strategies.md +0 -138
- package/global-skills/system-design/SKILL.md +0 -213
- package/global-skills/testing-patterns/SKILL.md +0 -430
- package/global-skills/testing-patterns/references/dependency-breaking-catalog.md +0 -586
- package/opencode-swarm-plugin-0.30.7.tgz +0 -0
- package/opencode-swarm-plugin-0.31.0.tgz +0 -0
- package/scripts/cleanup-test-memories.ts +0 -346
- package/scripts/init-skill.ts +0 -222
- package/scripts/migrate-unknown-sessions.ts +0 -349
- package/scripts/validate-skill.ts +0 -204
- package/src/agent-mail.ts +0 -1724
- package/src/anti-patterns.test.ts +0 -1167
- package/src/anti-patterns.ts +0 -448
- package/src/compaction-capture.integration.test.ts +0 -257
- package/src/compaction-hook.test.ts +0 -838
- package/src/compaction-hook.ts +0 -1204
- package/src/compaction-observability.integration.test.ts +0 -139
- package/src/compaction-observability.test.ts +0 -187
- package/src/compaction-observability.ts +0 -324
- package/src/compaction-prompt-scorers.test.ts +0 -475
- package/src/compaction-prompt-scoring.ts +0 -300
- package/src/contributor-tools.test.ts +0 -133
- package/src/contributor-tools.ts +0 -201
- package/src/dashboard.test.ts +0 -611
- package/src/dashboard.ts +0 -462
- package/src/error-enrichment.test.ts +0 -403
- package/src/error-enrichment.ts +0 -219
- package/src/eval-capture.test.ts +0 -1015
- package/src/eval-capture.ts +0 -929
- package/src/eval-gates.test.ts +0 -306
- package/src/eval-gates.ts +0 -218
- package/src/eval-history.test.ts +0 -508
- package/src/eval-history.ts +0 -214
- package/src/eval-learning.test.ts +0 -378
- package/src/eval-learning.ts +0 -360
- package/src/eval-runner.test.ts +0 -223
- package/src/eval-runner.ts +0 -402
- package/src/export-tools.test.ts +0 -476
- package/src/export-tools.ts +0 -257
- package/src/hive.integration.test.ts +0 -2241
- package/src/hive.ts +0 -1628
- package/src/index.ts +0 -940
- package/src/learning.integration.test.ts +0 -1815
- package/src/learning.ts +0 -1079
- package/src/logger.test.ts +0 -189
- package/src/logger.ts +0 -135
- package/src/mandate-promotion.test.ts +0 -473
- package/src/mandate-promotion.ts +0 -239
- package/src/mandate-storage.integration.test.ts +0 -601
- package/src/mandate-storage.test.ts +0 -578
- package/src/mandate-storage.ts +0 -794
- package/src/mandates.ts +0 -540
- package/src/memory-tools.test.ts +0 -195
- package/src/memory-tools.ts +0 -344
- package/src/memory.integration.test.ts +0 -334
- package/src/memory.test.ts +0 -158
- package/src/memory.ts +0 -527
- package/src/model-selection.test.ts +0 -188
- package/src/model-selection.ts +0 -68
- package/src/observability-tools.test.ts +0 -359
- package/src/observability-tools.ts +0 -871
- package/src/output-guardrails.test.ts +0 -438
- package/src/output-guardrails.ts +0 -381
- package/src/pattern-maturity.test.ts +0 -1160
- package/src/pattern-maturity.ts +0 -525
- package/src/planning-guardrails.test.ts +0 -491
- package/src/planning-guardrails.ts +0 -438
- package/src/plugin.ts +0 -23
- package/src/post-compaction-tracker.test.ts +0 -251
- package/src/post-compaction-tracker.ts +0 -237
- package/src/query-tools.test.ts +0 -636
- package/src/query-tools.ts +0 -324
- package/src/rate-limiter.integration.test.ts +0 -466
- package/src/rate-limiter.ts +0 -774
- package/src/replay-tools.test.ts +0 -496
- package/src/replay-tools.ts +0 -240
- package/src/repo-crawl.integration.test.ts +0 -441
- package/src/repo-crawl.ts +0 -610
- package/src/schemas/cell-events.test.ts +0 -347
- package/src/schemas/cell-events.ts +0 -807
- package/src/schemas/cell.ts +0 -257
- package/src/schemas/evaluation.ts +0 -166
- package/src/schemas/index.test.ts +0 -199
- package/src/schemas/index.ts +0 -286
- package/src/schemas/mandate.ts +0 -232
- package/src/schemas/swarm-context.ts +0 -115
- package/src/schemas/task.ts +0 -161
- package/src/schemas/worker-handoff.test.ts +0 -302
- package/src/schemas/worker-handoff.ts +0 -131
- package/src/sessions/agent-discovery.test.ts +0 -137
- package/src/sessions/agent-discovery.ts +0 -112
- package/src/sessions/index.ts +0 -15
- package/src/skills.integration.test.ts +0 -1192
- package/src/skills.test.ts +0 -643
- package/src/skills.ts +0 -1549
- package/src/storage.integration.test.ts +0 -341
- package/src/storage.ts +0 -884
- package/src/structured.integration.test.ts +0 -817
- package/src/structured.test.ts +0 -1046
- package/src/structured.ts +0 -762
- package/src/swarm-decompose.test.ts +0 -188
- package/src/swarm-decompose.ts +0 -1302
- package/src/swarm-deferred.integration.test.ts +0 -157
- package/src/swarm-deferred.test.ts +0 -38
- package/src/swarm-insights.test.ts +0 -214
- package/src/swarm-insights.ts +0 -459
- package/src/swarm-mail.integration.test.ts +0 -970
- package/src/swarm-mail.ts +0 -739
- package/src/swarm-orchestrate.integration.test.ts +0 -282
- package/src/swarm-orchestrate.test.ts +0 -548
- package/src/swarm-orchestrate.ts +0 -3084
- package/src/swarm-prompts.test.ts +0 -1270
- package/src/swarm-prompts.ts +0 -2077
- package/src/swarm-research.integration.test.ts +0 -701
- package/src/swarm-research.test.ts +0 -698
- package/src/swarm-research.ts +0 -472
- package/src/swarm-review.integration.test.ts +0 -285
- package/src/swarm-review.test.ts +0 -879
- package/src/swarm-review.ts +0 -709
- package/src/swarm-strategies.ts +0 -407
- package/src/swarm-worktree.test.ts +0 -501
- package/src/swarm-worktree.ts +0 -575
- package/src/swarm.integration.test.ts +0 -2377
- package/src/swarm.ts +0 -38
- package/src/tool-adapter.integration.test.ts +0 -1221
- package/src/tool-availability.ts +0 -461
- package/tsconfig.json +0 -28
package/src/eval-capture.ts
DELETED
|
@@ -1,929 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Eval Data Capture - Captures real swarm execution data for evals
|
|
3
|
-
*
|
|
4
|
-
* Records decomposition inputs, outputs, and outcomes to JSONL files
|
|
5
|
-
* that can be used as ground truth for Evalite evals.
|
|
6
|
-
*
|
|
7
|
-
* Data flow:
|
|
8
|
-
* 1. swarm_decompose captures: task, context, generated decomposition
|
|
9
|
-
* 2. swarm_complete captures: outcome signals per subtask
|
|
10
|
-
* 3. swarm_record_outcome captures: learning signals
|
|
11
|
-
* 4. Human feedback (optional): accept/reject/modify
|
|
12
|
-
* 5. Coordinator events: decisions, violations, outcomes, compaction
|
|
13
|
-
* 6. Session capture: full coordinator session to ~/.config/swarm-tools/sessions/
|
|
14
|
-
*
|
|
15
|
-
* Event types:
|
|
16
|
-
* - DECISION: strategy_selected, worker_spawned, review_completed, decomposition_complete, researcher_spawned, skill_loaded, inbox_checked, blocker_resolved, scope_change_approved, scope_change_rejected
|
|
17
|
-
* - VIOLATION: coordinator_edited_file, coordinator_ran_tests, coordinator_reserved_files, no_worker_spawned
|
|
18
|
-
* - OUTCOME: subtask_success, subtask_retry, subtask_failed, epic_complete, blocker_detected
|
|
19
|
-
* - COMPACTION: detection_complete, prompt_generated, context_injected, resumption_started, tool_call_tracked
|
|
20
|
-
*
|
|
21
|
-
* @module eval-capture
|
|
22
|
-
*/
|
|
23
|
-
import * as fs from "node:fs";
|
|
24
|
-
import * as os from "node:os";
|
|
25
|
-
import * as path from "node:path";
|
|
26
|
-
import { z } from "zod";
|
|
27
|
-
|
|
28
|
-
// ============================================================================
|
|
29
|
-
// Schemas
|
|
30
|
-
// ============================================================================
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Subtask outcome - what actually happened
|
|
34
|
-
*/
|
|
35
|
-
export const SubtaskOutcomeSchema = z.object({
|
|
36
|
-
/** Subtask bead ID */
|
|
37
|
-
bead_id: z.string(),
|
|
38
|
-
/** Subtask title */
|
|
39
|
-
title: z.string(),
|
|
40
|
-
/** Planned files */
|
|
41
|
-
planned_files: z.array(z.string()),
|
|
42
|
-
/** Actual files touched */
|
|
43
|
-
actual_files: z.array(z.string()),
|
|
44
|
-
/** Duration in ms */
|
|
45
|
-
duration_ms: z.number().int().min(0),
|
|
46
|
-
/** Error count */
|
|
47
|
-
error_count: z.number().int().min(0),
|
|
48
|
-
/** Retry count */
|
|
49
|
-
retry_count: z.number().int().min(0),
|
|
50
|
-
/** Success */
|
|
51
|
-
success: z.boolean(),
|
|
52
|
-
/** Failure mode if failed */
|
|
53
|
-
failure_mode: z.string().optional(),
|
|
54
|
-
});
|
|
55
|
-
export type SubtaskOutcome = z.infer<typeof SubtaskOutcomeSchema>;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Complete eval record - input, output, and outcome
|
|
59
|
-
*/
|
|
60
|
-
export const EvalRecordSchema = z.object({
|
|
61
|
-
/** Unique ID for this eval record */
|
|
62
|
-
id: z.string(),
|
|
63
|
-
/** Timestamp when decomposition was generated */
|
|
64
|
-
timestamp: z.string(), // ISO-8601
|
|
65
|
-
/** Project path */
|
|
66
|
-
project_path: z.string(),
|
|
67
|
-
|
|
68
|
-
// INPUT
|
|
69
|
-
/** Original task description */
|
|
70
|
-
task: z.string(),
|
|
71
|
-
/** Context provided (codebase info, CASS results, etc.) */
|
|
72
|
-
context: z.string().optional(),
|
|
73
|
-
/** Strategy used for decomposition */
|
|
74
|
-
strategy: z.enum(["file-based", "feature-based", "risk-based", "auto"]),
|
|
75
|
-
/** Number of subtasks generated */
|
|
76
|
-
subtask_count: z.number().int().min(1),
|
|
77
|
-
|
|
78
|
-
// OUTPUT (the decomposition)
|
|
79
|
-
/** Epic title */
|
|
80
|
-
epic_title: z.string(),
|
|
81
|
-
/** Epic description */
|
|
82
|
-
epic_description: z.string().optional(),
|
|
83
|
-
/** Generated subtasks */
|
|
84
|
-
subtasks: z.array(
|
|
85
|
-
z.object({
|
|
86
|
-
title: z.string(),
|
|
87
|
-
description: z.string().optional(),
|
|
88
|
-
files: z.array(z.string()),
|
|
89
|
-
dependencies: z.array(z.number()).optional(),
|
|
90
|
-
estimated_complexity: z.number().int().min(1).max(5).optional(),
|
|
91
|
-
}),
|
|
92
|
-
),
|
|
93
|
-
|
|
94
|
-
// OUTCOME (what actually happened)
|
|
95
|
-
/** Subtask outcomes */
|
|
96
|
-
outcomes: z.array(SubtaskOutcomeSchema).optional(),
|
|
97
|
-
/** Overall success (all subtasks succeeded) */
|
|
98
|
-
overall_success: z.boolean().optional(),
|
|
99
|
-
/** Total duration (sum of all subtasks) */
|
|
100
|
-
total_duration_ms: z.number().int().min(0).optional(),
|
|
101
|
-
/** Total errors across all subtasks */
|
|
102
|
-
total_errors: z.number().int().min(0).optional(),
|
|
103
|
-
|
|
104
|
-
// HUMAN FEEDBACK (optional)
|
|
105
|
-
/** Human accepted the decomposition as-is */
|
|
106
|
-
human_accepted: z.boolean().optional(),
|
|
107
|
-
/** Human modified the decomposition */
|
|
108
|
-
human_modified: z.boolean().optional(),
|
|
109
|
-
/** Human feedback notes */
|
|
110
|
-
human_notes: z.string().optional(),
|
|
111
|
-
|
|
112
|
-
// COMPUTED METRICS
|
|
113
|
-
/** File overlap between subtasks (should be 0) */
|
|
114
|
-
file_overlap_count: z.number().int().min(0).optional(),
|
|
115
|
-
/** Scope accuracy: actual files / planned files */
|
|
116
|
-
scope_accuracy: z.number().min(0).max(2).optional(),
|
|
117
|
-
/** Time balance: max duration / min duration (lower is better) */
|
|
118
|
-
time_balance_ratio: z.number().min(1).optional(),
|
|
119
|
-
});
|
|
120
|
-
export type EvalRecord = z.infer<typeof EvalRecordSchema>;
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Partial record for in-progress capture
|
|
124
|
-
*/
|
|
125
|
-
export type PartialEvalRecord = Partial<EvalRecord> & {
|
|
126
|
-
id: string;
|
|
127
|
-
timestamp: string;
|
|
128
|
-
task: string;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Coordinator Event - captures coordinator decisions, violations, outcomes, and compaction
|
|
133
|
-
*/
|
|
134
|
-
export const CoordinatorEventSchema = z.discriminatedUnion("event_type", [
|
|
135
|
-
// DECISION events
|
|
136
|
-
z.object({
|
|
137
|
-
session_id: z.string(),
|
|
138
|
-
epic_id: z.string(),
|
|
139
|
-
timestamp: z.string(),
|
|
140
|
-
event_type: z.literal("DECISION"),
|
|
141
|
-
decision_type: z.enum([
|
|
142
|
-
"strategy_selected",
|
|
143
|
-
"worker_spawned",
|
|
144
|
-
"review_completed",
|
|
145
|
-
"decomposition_complete",
|
|
146
|
-
"researcher_spawned",
|
|
147
|
-
"skill_loaded",
|
|
148
|
-
"inbox_checked",
|
|
149
|
-
"blocker_resolved",
|
|
150
|
-
"scope_change_approved",
|
|
151
|
-
"scope_change_rejected",
|
|
152
|
-
]),
|
|
153
|
-
payload: z.any(),
|
|
154
|
-
}),
|
|
155
|
-
// VIOLATION events
|
|
156
|
-
z.object({
|
|
157
|
-
session_id: z.string(),
|
|
158
|
-
epic_id: z.string(),
|
|
159
|
-
timestamp: z.string(),
|
|
160
|
-
event_type: z.literal("VIOLATION"),
|
|
161
|
-
violation_type: z.enum([
|
|
162
|
-
"coordinator_edited_file",
|
|
163
|
-
"coordinator_ran_tests",
|
|
164
|
-
"coordinator_reserved_files",
|
|
165
|
-
"no_worker_spawned",
|
|
166
|
-
]),
|
|
167
|
-
payload: z.any(),
|
|
168
|
-
}),
|
|
169
|
-
// OUTCOME events
|
|
170
|
-
z.object({
|
|
171
|
-
session_id: z.string(),
|
|
172
|
-
epic_id: z.string(),
|
|
173
|
-
timestamp: z.string(),
|
|
174
|
-
event_type: z.literal("OUTCOME"),
|
|
175
|
-
outcome_type: z.enum([
|
|
176
|
-
"subtask_success",
|
|
177
|
-
"subtask_retry",
|
|
178
|
-
"subtask_failed",
|
|
179
|
-
"epic_complete",
|
|
180
|
-
"blocker_detected",
|
|
181
|
-
]),
|
|
182
|
-
payload: z.any(),
|
|
183
|
-
}),
|
|
184
|
-
// COMPACTION events
|
|
185
|
-
z.object({
|
|
186
|
-
session_id: z.string(),
|
|
187
|
-
epic_id: z.string(),
|
|
188
|
-
timestamp: z.string(),
|
|
189
|
-
event_type: z.literal("COMPACTION"),
|
|
190
|
-
compaction_type: z.enum([
|
|
191
|
-
"detection_complete",
|
|
192
|
-
"prompt_generated",
|
|
193
|
-
"context_injected",
|
|
194
|
-
"resumption_started",
|
|
195
|
-
"tool_call_tracked",
|
|
196
|
-
]),
|
|
197
|
-
payload: z.any(),
|
|
198
|
-
}),
|
|
199
|
-
]);
|
|
200
|
-
export type CoordinatorEvent = z.infer<typeof CoordinatorEventSchema>;
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Coordinator Session - wraps a full coordinator session
|
|
204
|
-
*/
|
|
205
|
-
export const CoordinatorSessionSchema = z.object({
|
|
206
|
-
session_id: z.string(),
|
|
207
|
-
epic_id: z.string(),
|
|
208
|
-
start_time: z.string(),
|
|
209
|
-
end_time: z.string().optional(),
|
|
210
|
-
events: z.array(CoordinatorEventSchema),
|
|
211
|
-
});
|
|
212
|
-
export type CoordinatorSession = z.infer<typeof CoordinatorSessionSchema>;
|
|
213
|
-
|
|
214
|
-
// ============================================================================
|
|
215
|
-
// Storage
|
|
216
|
-
// ============================================================================
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Default path for eval data
|
|
220
|
-
*/
|
|
221
|
-
export const DEFAULT_EVAL_DATA_PATH = ".opencode/eval-data.jsonl";
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Get the eval data file path for a project
|
|
225
|
-
*/
|
|
226
|
-
export function getEvalDataPath(projectPath: string): string {
|
|
227
|
-
return path.join(projectPath, DEFAULT_EVAL_DATA_PATH);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Ensure the eval data directory exists
|
|
232
|
-
*/
|
|
233
|
-
export function ensureEvalDataDir(projectPath: string): void {
|
|
234
|
-
const evalPath = getEvalDataPath(projectPath);
|
|
235
|
-
const dir = path.dirname(evalPath);
|
|
236
|
-
if (!fs.existsSync(dir)) {
|
|
237
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Append an eval record to the JSONL file
|
|
243
|
-
*/
|
|
244
|
-
export function appendEvalRecord(
|
|
245
|
-
projectPath: string,
|
|
246
|
-
record: EvalRecord | PartialEvalRecord,
|
|
247
|
-
): void {
|
|
248
|
-
ensureEvalDataDir(projectPath);
|
|
249
|
-
const evalPath = getEvalDataPath(projectPath);
|
|
250
|
-
const line = `${JSON.stringify(record)}\n`;
|
|
251
|
-
fs.appendFileSync(evalPath, line, "utf-8");
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Read all eval records from a project
|
|
256
|
-
*/
|
|
257
|
-
export function readEvalRecords(projectPath: string): EvalRecord[] {
|
|
258
|
-
const evalPath = getEvalDataPath(projectPath);
|
|
259
|
-
if (!fs.existsSync(evalPath)) {
|
|
260
|
-
return [];
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const content = fs.readFileSync(evalPath, "utf-8");
|
|
264
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
265
|
-
|
|
266
|
-
return lines.map((line) => {
|
|
267
|
-
const parsed = JSON.parse(line);
|
|
268
|
-
return EvalRecordSchema.parse(parsed);
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Read partial records (for updating in-progress records)
|
|
274
|
-
*/
|
|
275
|
-
export function readPartialRecords(projectPath: string): PartialEvalRecord[] {
|
|
276
|
-
const evalPath = getEvalDataPath(projectPath);
|
|
277
|
-
if (!fs.existsSync(evalPath)) {
|
|
278
|
-
return [];
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const content = fs.readFileSync(evalPath, "utf-8");
|
|
282
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
283
|
-
|
|
284
|
-
return lines.map((line) => JSON.parse(line) as PartialEvalRecord);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Update an existing record by ID
|
|
289
|
-
*/
|
|
290
|
-
export function updateEvalRecord(
|
|
291
|
-
projectPath: string,
|
|
292
|
-
id: string,
|
|
293
|
-
updates: Partial<EvalRecord>,
|
|
294
|
-
): boolean {
|
|
295
|
-
const records = readPartialRecords(projectPath);
|
|
296
|
-
const index = records.findIndex((r) => r.id === id);
|
|
297
|
-
|
|
298
|
-
if (index === -1) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
records[index] = { ...records[index], ...updates };
|
|
303
|
-
|
|
304
|
-
// Rewrite the file
|
|
305
|
-
const evalPath = getEvalDataPath(projectPath);
|
|
306
|
-
const content = `${records.map((r) => JSON.stringify(r)).join("\n")}\n`;
|
|
307
|
-
fs.writeFileSync(evalPath, content, "utf-8");
|
|
308
|
-
|
|
309
|
-
return true;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// ============================================================================
|
|
313
|
-
// Capture Functions
|
|
314
|
-
// ============================================================================
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* In-memory store for in-progress records (keyed by epic ID)
|
|
318
|
-
*/
|
|
319
|
-
const inProgressRecords = new Map<string, PartialEvalRecord>();
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Start capturing a decomposition
|
|
323
|
-
*
|
|
324
|
-
* Called when swarm_decompose generates a decomposition.
|
|
325
|
-
* Creates a partial record that will be completed when outcomes arrive.
|
|
326
|
-
*/
|
|
327
|
-
export function captureDecomposition(params: {
|
|
328
|
-
epicId: string;
|
|
329
|
-
projectPath: string;
|
|
330
|
-
task: string;
|
|
331
|
-
context?: string;
|
|
332
|
-
strategy: "file-based" | "feature-based" | "risk-based" | "auto";
|
|
333
|
-
epicTitle: string;
|
|
334
|
-
epicDescription?: string;
|
|
335
|
-
subtasks: Array<{
|
|
336
|
-
title: string;
|
|
337
|
-
description?: string;
|
|
338
|
-
files: string[];
|
|
339
|
-
dependencies?: number[];
|
|
340
|
-
estimated_complexity?: number;
|
|
341
|
-
}>;
|
|
342
|
-
}): PartialEvalRecord {
|
|
343
|
-
const record: PartialEvalRecord = {
|
|
344
|
-
id: params.epicId,
|
|
345
|
-
timestamp: new Date().toISOString(),
|
|
346
|
-
project_path: params.projectPath,
|
|
347
|
-
task: params.task,
|
|
348
|
-
context: params.context,
|
|
349
|
-
strategy: params.strategy,
|
|
350
|
-
subtask_count: params.subtasks.length,
|
|
351
|
-
epic_title: params.epicTitle,
|
|
352
|
-
epic_description: params.epicDescription,
|
|
353
|
-
subtasks: params.subtasks,
|
|
354
|
-
outcomes: [],
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
// Store in memory for later updates
|
|
358
|
-
inProgressRecords.set(params.epicId, record);
|
|
359
|
-
|
|
360
|
-
// Also persist to disk (partial)
|
|
361
|
-
appendEvalRecord(params.projectPath, record);
|
|
362
|
-
|
|
363
|
-
return record;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Capture a subtask outcome
|
|
368
|
-
*
|
|
369
|
-
* Called when swarm_complete finishes a subtask.
|
|
370
|
-
* Updates the in-progress record with outcome data.
|
|
371
|
-
*/
|
|
372
|
-
export function captureSubtaskOutcome(params: {
|
|
373
|
-
epicId: string;
|
|
374
|
-
projectPath: string;
|
|
375
|
-
beadId: string;
|
|
376
|
-
title: string;
|
|
377
|
-
plannedFiles: string[];
|
|
378
|
-
actualFiles: string[];
|
|
379
|
-
durationMs: number;
|
|
380
|
-
errorCount: number;
|
|
381
|
-
retryCount: number;
|
|
382
|
-
success: boolean;
|
|
383
|
-
failureMode?: string;
|
|
384
|
-
}): void {
|
|
385
|
-
const outcome: SubtaskOutcome = {
|
|
386
|
-
bead_id: params.beadId,
|
|
387
|
-
title: params.title,
|
|
388
|
-
planned_files: params.plannedFiles,
|
|
389
|
-
actual_files: params.actualFiles,
|
|
390
|
-
duration_ms: params.durationMs,
|
|
391
|
-
error_count: params.errorCount,
|
|
392
|
-
retry_count: params.retryCount,
|
|
393
|
-
success: params.success,
|
|
394
|
-
failure_mode: params.failureMode,
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
// Update in-memory record
|
|
398
|
-
const record = inProgressRecords.get(params.epicId);
|
|
399
|
-
if (record) {
|
|
400
|
-
record.outcomes = record.outcomes || [];
|
|
401
|
-
record.outcomes.push(outcome);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Update on disk
|
|
405
|
-
updateEvalRecord(params.projectPath, params.epicId, {
|
|
406
|
-
outcomes: record?.outcomes,
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Finalize an eval record
|
|
412
|
-
*
|
|
413
|
-
* Called when all subtasks are complete.
|
|
414
|
-
* Computes aggregate metrics and marks record as complete.
|
|
415
|
-
*/
|
|
416
|
-
export function finalizeEvalRecord(params: {
|
|
417
|
-
epicId: string;
|
|
418
|
-
projectPath: string;
|
|
419
|
-
}): EvalRecord | null {
|
|
420
|
-
const record = inProgressRecords.get(params.epicId);
|
|
421
|
-
if (!record || !record.outcomes || record.outcomes.length === 0) {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Compute aggregate metrics
|
|
426
|
-
const outcomes = record.outcomes;
|
|
427
|
-
|
|
428
|
-
const overallSuccess = outcomes.every((o) => o.success);
|
|
429
|
-
const totalDurationMs = outcomes.reduce((sum, o) => sum + o.duration_ms, 0);
|
|
430
|
-
const totalErrors = outcomes.reduce((sum, o) => sum + o.error_count, 0);
|
|
431
|
-
|
|
432
|
-
// File overlap: count files that appear in multiple subtasks
|
|
433
|
-
const allPlannedFiles = record.subtasks?.flatMap((s) => s.files) || [];
|
|
434
|
-
const fileOccurrences = new Map<string, number>();
|
|
435
|
-
for (const file of allPlannedFiles) {
|
|
436
|
-
fileOccurrences.set(file, (fileOccurrences.get(file) || 0) + 1);
|
|
437
|
-
}
|
|
438
|
-
const fileOverlapCount = Array.from(fileOccurrences.values()).filter(
|
|
439
|
-
(count) => count > 1,
|
|
440
|
-
).length;
|
|
441
|
-
|
|
442
|
-
// Scope accuracy: actual files / planned files
|
|
443
|
-
const plannedFileSet = new Set(allPlannedFiles);
|
|
444
|
-
const actualFileSet = new Set(outcomes.flatMap((o) => o.actual_files));
|
|
445
|
-
const scopeAccuracy =
|
|
446
|
-
plannedFileSet.size > 0 ? actualFileSet.size / plannedFileSet.size : 1;
|
|
447
|
-
|
|
448
|
-
// Time balance: max duration / min duration
|
|
449
|
-
const durations = outcomes.map((o) => o.duration_ms).filter((d) => d > 0);
|
|
450
|
-
const timeBalanceRatio =
|
|
451
|
-
durations.length > 1 ? Math.max(...durations) / Math.min(...durations) : 1;
|
|
452
|
-
|
|
453
|
-
// Update record with computed metrics
|
|
454
|
-
const finalRecord: EvalRecord = {
|
|
455
|
-
...(record as EvalRecord),
|
|
456
|
-
overall_success: overallSuccess,
|
|
457
|
-
total_duration_ms: totalDurationMs,
|
|
458
|
-
total_errors: totalErrors,
|
|
459
|
-
file_overlap_count: fileOverlapCount,
|
|
460
|
-
scope_accuracy: scopeAccuracy,
|
|
461
|
-
time_balance_ratio: timeBalanceRatio,
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
// Update on disk
|
|
465
|
-
updateEvalRecord(params.projectPath, params.epicId, finalRecord);
|
|
466
|
-
|
|
467
|
-
// Remove from in-progress
|
|
468
|
-
inProgressRecords.delete(params.epicId);
|
|
469
|
-
|
|
470
|
-
return finalRecord;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Capture human feedback on a decomposition
|
|
475
|
-
*/
|
|
476
|
-
export function captureHumanFeedback(params: {
|
|
477
|
-
epicId: string;
|
|
478
|
-
projectPath: string;
|
|
479
|
-
accepted: boolean;
|
|
480
|
-
modified: boolean;
|
|
481
|
-
notes?: string;
|
|
482
|
-
}): void {
|
|
483
|
-
updateEvalRecord(params.projectPath, params.epicId, {
|
|
484
|
-
human_accepted: params.accepted,
|
|
485
|
-
human_modified: params.modified,
|
|
486
|
-
human_notes: params.notes,
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// ============================================================================
|
|
491
|
-
// Eval Data Export
|
|
492
|
-
// ============================================================================
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Export eval records as Evalite-compatible test cases
|
|
496
|
-
*
|
|
497
|
-
* Filters to only complete records with outcomes.
|
|
498
|
-
*/
|
|
499
|
-
export function exportForEvalite(projectPath: string): Array<{
|
|
500
|
-
input: { task: string; context?: string };
|
|
501
|
-
expected: {
|
|
502
|
-
minSubtasks: number;
|
|
503
|
-
subtaskCount: number;
|
|
504
|
-
requiredFiles?: string[];
|
|
505
|
-
overallSuccess?: boolean;
|
|
506
|
-
};
|
|
507
|
-
actual: EvalRecord;
|
|
508
|
-
}> {
|
|
509
|
-
const records = readEvalRecords(projectPath);
|
|
510
|
-
|
|
511
|
-
return records
|
|
512
|
-
.filter((r) => r.outcomes && r.outcomes.length > 0)
|
|
513
|
-
.map((record) => ({
|
|
514
|
-
input: {
|
|
515
|
-
task: record.task,
|
|
516
|
-
context: record.context,
|
|
517
|
-
},
|
|
518
|
-
expected: {
|
|
519
|
-
minSubtasks: 2,
|
|
520
|
-
subtaskCount: record.subtask_count,
|
|
521
|
-
requiredFiles: record.subtasks.flatMap((s) => s.files),
|
|
522
|
-
overallSuccess: record.overall_success,
|
|
523
|
-
},
|
|
524
|
-
actual: record,
|
|
525
|
-
}));
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Get statistics about captured eval data
|
|
530
|
-
*/
|
|
531
|
-
export function getEvalDataStats(projectPath: string): {
|
|
532
|
-
totalRecords: number;
|
|
533
|
-
completeRecords: number;
|
|
534
|
-
successRate: number;
|
|
535
|
-
avgSubtasks: number;
|
|
536
|
-
avgDurationMs: number;
|
|
537
|
-
avgScopeAccuracy: number;
|
|
538
|
-
avgTimeBalance: number;
|
|
539
|
-
} {
|
|
540
|
-
const records = readEvalRecords(projectPath);
|
|
541
|
-
const complete = records.filter((r) => r.outcomes && r.outcomes.length > 0);
|
|
542
|
-
|
|
543
|
-
if (complete.length === 0) {
|
|
544
|
-
return {
|
|
545
|
-
totalRecords: records.length,
|
|
546
|
-
completeRecords: 0,
|
|
547
|
-
successRate: 0,
|
|
548
|
-
avgSubtasks: 0,
|
|
549
|
-
avgDurationMs: 0,
|
|
550
|
-
avgScopeAccuracy: 0,
|
|
551
|
-
avgTimeBalance: 0,
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const successCount = complete.filter((r) => r.overall_success).length;
|
|
556
|
-
const avgSubtasks =
|
|
557
|
-
complete.reduce((sum, r) => sum + (r.outcomes?.length || 0), 0) /
|
|
558
|
-
complete.length;
|
|
559
|
-
const avgDurationMs =
|
|
560
|
-
complete.reduce((sum, r) => sum + (r.total_duration_ms || 0), 0) /
|
|
561
|
-
complete.length;
|
|
562
|
-
const avgScopeAccuracy =
|
|
563
|
-
complete.reduce((sum, r) => sum + (r.scope_accuracy || 1), 0) /
|
|
564
|
-
complete.length;
|
|
565
|
-
const avgTimeBalance =
|
|
566
|
-
complete.reduce((sum, r) => sum + (r.time_balance_ratio || 1), 0) /
|
|
567
|
-
complete.length;
|
|
568
|
-
|
|
569
|
-
return {
|
|
570
|
-
totalRecords: records.length,
|
|
571
|
-
completeRecords: complete.length,
|
|
572
|
-
successRate: successCount / complete.length,
|
|
573
|
-
avgSubtasks,
|
|
574
|
-
avgDurationMs,
|
|
575
|
-
avgScopeAccuracy,
|
|
576
|
-
avgTimeBalance,
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// ============================================================================
|
|
581
|
-
// Coordinator Session Capture
|
|
582
|
-
// ============================================================================
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Get the session directory path
|
|
586
|
-
*/
|
|
587
|
-
export function getSessionDir(): string {
|
|
588
|
-
return path.join(os.homedir(), ".config", "swarm-tools", "sessions");
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* Get the session file path for a session ID
|
|
593
|
-
*/
|
|
594
|
-
export function getSessionPath(sessionId: string): string {
|
|
595
|
-
return path.join(getSessionDir(), `${sessionId}.jsonl`);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Ensure the session directory exists
|
|
600
|
-
*/
|
|
601
|
-
export function ensureSessionDir(): void {
|
|
602
|
-
const sessionDir = getSessionDir();
|
|
603
|
-
if (!fs.existsSync(sessionDir)) {
|
|
604
|
-
fs.mkdirSync(sessionDir, { recursive: true });
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/**
|
|
609
|
-
* Capture a coordinator event to the session file
|
|
610
|
-
*
|
|
611
|
-
* Appends the event as a JSONL line to ~/.config/swarm-tools/sessions/{session_id}.jsonl
|
|
612
|
-
*/
|
|
613
|
-
export function captureCoordinatorEvent(event: CoordinatorEvent): void {
|
|
614
|
-
// Validate event
|
|
615
|
-
CoordinatorEventSchema.parse(event);
|
|
616
|
-
|
|
617
|
-
// Ensure directory exists
|
|
618
|
-
ensureSessionDir();
|
|
619
|
-
|
|
620
|
-
// Append to session file
|
|
621
|
-
const sessionPath = getSessionPath(event.session_id);
|
|
622
|
-
const line = `${JSON.stringify(event)}\n`;
|
|
623
|
-
fs.appendFileSync(sessionPath, line, "utf-8");
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Capture a compaction event to the session file
|
|
628
|
-
*
|
|
629
|
-
* Helper for capturing COMPACTION events with automatic timestamp generation.
|
|
630
|
-
* Tracks compaction hook lifecycle: detection → prompt generation → context injection → resumption.
|
|
631
|
-
*
|
|
632
|
-
* **Part of eval-driven development pipeline:** Compaction events are used by `compaction-prompt.eval.ts`
|
|
633
|
-
* to score prompt quality (ID specificity, actionability, coordinator identity).
|
|
634
|
-
*
|
|
635
|
-
* **Lifecycle stages:**
|
|
636
|
-
* - `detection_complete` - Compaction detected (confidence level, context type)
|
|
637
|
-
* - `prompt_generated` - Continuation prompt created (FULL content stored for eval)
|
|
638
|
-
* - `context_injected` - Prompt injected into OpenCode context
|
|
639
|
-
* - `resumption_started` - Coordinator resumed from checkpoint
|
|
640
|
-
* - `tool_call_tracked` - First tool called post-compaction (measures discipline)
|
|
641
|
-
*
|
|
642
|
-
* @param params - Compaction event parameters
|
|
643
|
-
* @param params.session_id - Coordinator session ID
|
|
644
|
-
* @param params.epic_id - Epic ID being coordinated
|
|
645
|
-
* @param params.compaction_type - Stage of compaction lifecycle
|
|
646
|
-
* @param params.payload - Event-specific data (full prompt content, detection results, etc.)
|
|
647
|
-
*
|
|
648
|
-
* @example
|
|
649
|
-
* // Capture detection complete
|
|
650
|
-
* captureCompactionEvent({
|
|
651
|
-
* session_id: "session-123",
|
|
652
|
-
* epic_id: "bd-456",
|
|
653
|
-
* compaction_type: "detection_complete",
|
|
654
|
-
* payload: {
|
|
655
|
-
* confidence: "high",
|
|
656
|
-
* context_type: "full",
|
|
657
|
-
* epic_id: "bd-456",
|
|
658
|
-
* },
|
|
659
|
-
* });
|
|
660
|
-
*
|
|
661
|
-
* @example
|
|
662
|
-
* // Capture prompt generated (with full content for eval)
|
|
663
|
-
* captureCompactionEvent({
|
|
664
|
-
* session_id: "session-123",
|
|
665
|
-
* epic_id: "bd-456",
|
|
666
|
-
* compaction_type: "prompt_generated",
|
|
667
|
-
* payload: {
|
|
668
|
-
* prompt_length: 5000,
|
|
669
|
-
* full_prompt: "You are a coordinator...", // Full prompt, not truncated - used for quality scoring
|
|
670
|
-
* context_type: "full",
|
|
671
|
-
* },
|
|
672
|
-
* });
|
|
673
|
-
*/
|
|
674
|
-
export function captureCompactionEvent(params: {
|
|
675
|
-
session_id: string;
|
|
676
|
-
epic_id: string;
|
|
677
|
-
compaction_type:
|
|
678
|
-
| "detection_complete"
|
|
679
|
-
| "prompt_generated"
|
|
680
|
-
| "context_injected"
|
|
681
|
-
| "resumption_started"
|
|
682
|
-
| "tool_call_tracked";
|
|
683
|
-
payload: any;
|
|
684
|
-
}): void {
|
|
685
|
-
const event: CoordinatorEvent = {
|
|
686
|
-
session_id: params.session_id,
|
|
687
|
-
epic_id: params.epic_id,
|
|
688
|
-
timestamp: new Date().toISOString(),
|
|
689
|
-
event_type: "COMPACTION",
|
|
690
|
-
compaction_type: params.compaction_type,
|
|
691
|
-
payload: params.payload,
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
captureCoordinatorEvent(event);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* Capture a researcher spawned event
|
|
699
|
-
*
|
|
700
|
-
* Called when coordinator spawns a swarm-researcher to handle unfamiliar technology
|
|
701
|
-
* or gather documentation before decomposition.
|
|
702
|
-
*/
|
|
703
|
-
export function captureResearcherSpawned(params: {
|
|
704
|
-
session_id: string;
|
|
705
|
-
epic_id: string;
|
|
706
|
-
researcher_id: string;
|
|
707
|
-
research_topic: string;
|
|
708
|
-
tools_used?: string[];
|
|
709
|
-
}): void {
|
|
710
|
-
const event: CoordinatorEvent = {
|
|
711
|
-
session_id: params.session_id,
|
|
712
|
-
epic_id: params.epic_id,
|
|
713
|
-
timestamp: new Date().toISOString(),
|
|
714
|
-
event_type: "DECISION",
|
|
715
|
-
decision_type: "researcher_spawned",
|
|
716
|
-
payload: {
|
|
717
|
-
researcher_id: params.researcher_id,
|
|
718
|
-
research_topic: params.research_topic,
|
|
719
|
-
tools_used: params.tools_used || [],
|
|
720
|
-
},
|
|
721
|
-
};
|
|
722
|
-
|
|
723
|
-
captureCoordinatorEvent(event);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
/**
|
|
727
|
-
* Capture a skill loaded event
|
|
728
|
-
*
|
|
729
|
-
* Called when coordinator loads domain knowledge via skills_use().
|
|
730
|
-
*/
|
|
731
|
-
export function captureSkillLoaded(params: {
|
|
732
|
-
session_id: string;
|
|
733
|
-
epic_id: string;
|
|
734
|
-
skill_name: string;
|
|
735
|
-
context?: string;
|
|
736
|
-
}): void {
|
|
737
|
-
const event: CoordinatorEvent = {
|
|
738
|
-
session_id: params.session_id,
|
|
739
|
-
epic_id: params.epic_id,
|
|
740
|
-
timestamp: new Date().toISOString(),
|
|
741
|
-
event_type: "DECISION",
|
|
742
|
-
decision_type: "skill_loaded",
|
|
743
|
-
payload: {
|
|
744
|
-
skill_name: params.skill_name,
|
|
745
|
-
context: params.context,
|
|
746
|
-
},
|
|
747
|
-
};
|
|
748
|
-
|
|
749
|
-
captureCoordinatorEvent(event);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* Capture an inbox checked event
|
|
754
|
-
*
|
|
755
|
-
* Called when coordinator checks swarmmail inbox for worker messages.
|
|
756
|
-
* Tracks monitoring frequency and responsiveness.
|
|
757
|
-
*/
|
|
758
|
-
export function captureInboxChecked(params: {
|
|
759
|
-
session_id: string;
|
|
760
|
-
epic_id: string;
|
|
761
|
-
message_count: number;
|
|
762
|
-
urgent_count: number;
|
|
763
|
-
}): void {
|
|
764
|
-
const event: CoordinatorEvent = {
|
|
765
|
-
session_id: params.session_id,
|
|
766
|
-
epic_id: params.epic_id,
|
|
767
|
-
timestamp: new Date().toISOString(),
|
|
768
|
-
event_type: "DECISION",
|
|
769
|
-
decision_type: "inbox_checked",
|
|
770
|
-
payload: {
|
|
771
|
-
message_count: params.message_count,
|
|
772
|
-
urgent_count: params.urgent_count,
|
|
773
|
-
},
|
|
774
|
-
};
|
|
775
|
-
|
|
776
|
-
captureCoordinatorEvent(event);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Capture a blocker resolved event
|
|
781
|
-
*
|
|
782
|
-
* Called when coordinator successfully unblocks a worker.
|
|
783
|
-
*/
|
|
784
|
-
export function captureBlockerResolved(params: {
|
|
785
|
-
session_id: string;
|
|
786
|
-
epic_id: string;
|
|
787
|
-
worker_id: string;
|
|
788
|
-
subtask_id: string;
|
|
789
|
-
blocker_type: string;
|
|
790
|
-
resolution: string;
|
|
791
|
-
}): void {
|
|
792
|
-
const event: CoordinatorEvent = {
|
|
793
|
-
session_id: params.session_id,
|
|
794
|
-
epic_id: params.epic_id,
|
|
795
|
-
timestamp: new Date().toISOString(),
|
|
796
|
-
event_type: "DECISION",
|
|
797
|
-
decision_type: "blocker_resolved",
|
|
798
|
-
payload: {
|
|
799
|
-
worker_id: params.worker_id,
|
|
800
|
-
subtask_id: params.subtask_id,
|
|
801
|
-
blocker_type: params.blocker_type,
|
|
802
|
-
resolution: params.resolution,
|
|
803
|
-
},
|
|
804
|
-
};
|
|
805
|
-
|
|
806
|
-
captureCoordinatorEvent(event);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Capture a scope change decision event
|
|
811
|
-
*
|
|
812
|
-
* Called when coordinator approves or rejects a worker's scope expansion request.
|
|
813
|
-
*/
|
|
814
|
-
export function captureScopeChangeDecision(params: {
|
|
815
|
-
session_id: string;
|
|
816
|
-
epic_id: string;
|
|
817
|
-
worker_id: string;
|
|
818
|
-
subtask_id: string;
|
|
819
|
-
approved: boolean;
|
|
820
|
-
original_scope?: string;
|
|
821
|
-
new_scope?: string;
|
|
822
|
-
requested_scope?: string;
|
|
823
|
-
rejection_reason?: string;
|
|
824
|
-
estimated_time_add?: number;
|
|
825
|
-
}): void {
|
|
826
|
-
const event: CoordinatorEvent = {
|
|
827
|
-
session_id: params.session_id,
|
|
828
|
-
epic_id: params.epic_id,
|
|
829
|
-
timestamp: new Date().toISOString(),
|
|
830
|
-
event_type: "DECISION",
|
|
831
|
-
decision_type: params.approved ? "scope_change_approved" : "scope_change_rejected",
|
|
832
|
-
payload: params.approved
|
|
833
|
-
? {
|
|
834
|
-
worker_id: params.worker_id,
|
|
835
|
-
subtask_id: params.subtask_id,
|
|
836
|
-
original_scope: params.original_scope,
|
|
837
|
-
new_scope: params.new_scope,
|
|
838
|
-
estimated_time_add: params.estimated_time_add,
|
|
839
|
-
}
|
|
840
|
-
: {
|
|
841
|
-
worker_id: params.worker_id,
|
|
842
|
-
subtask_id: params.subtask_id,
|
|
843
|
-
requested_scope: params.requested_scope,
|
|
844
|
-
rejection_reason: params.rejection_reason,
|
|
845
|
-
},
|
|
846
|
-
};
|
|
847
|
-
|
|
848
|
-
captureCoordinatorEvent(event);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
/**
|
|
852
|
-
* Capture a blocker detected event
|
|
853
|
-
*
|
|
854
|
-
* Called when a worker reports being blocked (OUTCOME event, not DECISION).
|
|
855
|
-
*/
|
|
856
|
-
export function captureBlockerDetected(params: {
|
|
857
|
-
session_id: string;
|
|
858
|
-
epic_id: string;
|
|
859
|
-
worker_id: string;
|
|
860
|
-
subtask_id: string;
|
|
861
|
-
blocker_type: string;
|
|
862
|
-
blocker_description: string;
|
|
863
|
-
}): void {
|
|
864
|
-
const event: CoordinatorEvent = {
|
|
865
|
-
session_id: params.session_id,
|
|
866
|
-
epic_id: params.epic_id,
|
|
867
|
-
timestamp: new Date().toISOString(),
|
|
868
|
-
event_type: "OUTCOME",
|
|
869
|
-
outcome_type: "blocker_detected",
|
|
870
|
-
payload: {
|
|
871
|
-
worker_id: params.worker_id,
|
|
872
|
-
subtask_id: params.subtask_id,
|
|
873
|
-
blocker_type: params.blocker_type,
|
|
874
|
-
blocker_description: params.blocker_description,
|
|
875
|
-
reported_at: new Date().toISOString(),
|
|
876
|
-
},
|
|
877
|
-
};
|
|
878
|
-
|
|
879
|
-
captureCoordinatorEvent(event);
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
/**
|
|
883
|
-
* Read all events from a session file
|
|
884
|
-
*/
|
|
885
|
-
export function readSessionEvents(sessionId: string): CoordinatorEvent[] {
|
|
886
|
-
const sessionPath = getSessionPath(sessionId);
|
|
887
|
-
if (!fs.existsSync(sessionPath)) {
|
|
888
|
-
return [];
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
const content = fs.readFileSync(sessionPath, "utf-8");
|
|
892
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
893
|
-
|
|
894
|
-
return lines.map((line) => {
|
|
895
|
-
const parsed = JSON.parse(line);
|
|
896
|
-
return CoordinatorEventSchema.parse(parsed);
|
|
897
|
-
});
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
/**
|
|
901
|
-
* Save a session - wraps all events in a CoordinatorSession structure
|
|
902
|
-
*
|
|
903
|
-
* Reads all events from the session file and wraps them in a session object.
|
|
904
|
-
* Returns null if the session file doesn't exist.
|
|
905
|
-
*/
|
|
906
|
-
export function saveSession(params: {
|
|
907
|
-
session_id: string;
|
|
908
|
-
epic_id: string;
|
|
909
|
-
}): CoordinatorSession | null {
|
|
910
|
-
const events = readSessionEvents(params.session_id);
|
|
911
|
-
if (events.length === 0) {
|
|
912
|
-
return null;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// Get timestamps from events
|
|
916
|
-
const timestamps = events.map((e) => new Date(e.timestamp).getTime());
|
|
917
|
-
const startTime = new Date(Math.min(...timestamps)).toISOString();
|
|
918
|
-
const endTime = new Date(Math.max(...timestamps)).toISOString();
|
|
919
|
-
|
|
920
|
-
const session: CoordinatorSession = {
|
|
921
|
-
session_id: params.session_id,
|
|
922
|
-
epic_id: params.epic_id,
|
|
923
|
-
start_time: startTime,
|
|
924
|
-
end_time: endTime,
|
|
925
|
-
events,
|
|
926
|
-
};
|
|
927
|
-
|
|
928
|
-
return session;
|
|
929
|
-
}
|