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/replay-tools.ts
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Replay Tools - Event replay with timing simulation
|
|
3
|
-
*
|
|
4
|
-
* TDD GREEN: Minimal implementation to pass tests
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
|
|
10
|
-
// ============================================================================
|
|
11
|
-
// Types
|
|
12
|
-
// ============================================================================
|
|
13
|
-
|
|
14
|
-
export type ReplaySpeed = "1x" | "2x" | "instant";
|
|
15
|
-
|
|
16
|
-
export interface ReplayEvent {
|
|
17
|
-
session_id: string;
|
|
18
|
-
epic_id: string;
|
|
19
|
-
timestamp: string;
|
|
20
|
-
event_type: "DECISION" | "VIOLATION" | "OUTCOME" | "COMPACTION";
|
|
21
|
-
decision_type?: string;
|
|
22
|
-
violation_type?: string;
|
|
23
|
-
outcome_type?: string;
|
|
24
|
-
payload: Record<string, unknown>;
|
|
25
|
-
delta_ms: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ReplayFilter {
|
|
29
|
-
type?: Array<"DECISION" | "VIOLATION" | "OUTCOME" | "COMPACTION">;
|
|
30
|
-
agent?: string;
|
|
31
|
-
since?: Date;
|
|
32
|
-
until?: Date;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ============================================================================
|
|
36
|
-
// fetchEpicEvents - Read JSONL and calculate deltas
|
|
37
|
-
// ============================================================================
|
|
38
|
-
|
|
39
|
-
export async function fetchEpicEvents(
|
|
40
|
-
epicId: string,
|
|
41
|
-
sessionFile: string,
|
|
42
|
-
): Promise<ReplayEvent[]> {
|
|
43
|
-
// Handle non-existent files
|
|
44
|
-
if (!existsSync(sessionFile)) {
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const content = readFileSync(sessionFile, "utf-8");
|
|
50
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
51
|
-
|
|
52
|
-
if (lines.length === 0) {
|
|
53
|
-
return [];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Parse JSONL
|
|
57
|
-
const events = lines.map((line) => JSON.parse(line));
|
|
58
|
-
|
|
59
|
-
// Sort by timestamp (chronological order)
|
|
60
|
-
events.sort(
|
|
61
|
-
(a, b) =>
|
|
62
|
-
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Calculate delta_ms between events
|
|
66
|
-
const replayEvents: ReplayEvent[] = [];
|
|
67
|
-
let prevTime = 0;
|
|
68
|
-
|
|
69
|
-
for (const event of events) {
|
|
70
|
-
const currTime = new Date(event.timestamp).getTime();
|
|
71
|
-
const delta_ms = prevTime === 0 ? 0 : currTime - prevTime;
|
|
72
|
-
|
|
73
|
-
replayEvents.push({
|
|
74
|
-
...event,
|
|
75
|
-
delta_ms,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
prevTime = currTime;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return replayEvents;
|
|
82
|
-
} catch (error) {
|
|
83
|
-
// Handle errors (e.g., invalid JSON)
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ============================================================================
|
|
89
|
-
// filterEvents - Filter by type/agent/time (AND logic)
|
|
90
|
-
// ============================================================================
|
|
91
|
-
|
|
92
|
-
export function filterEvents(
|
|
93
|
-
events: ReplayEvent[],
|
|
94
|
-
filter: ReplayFilter,
|
|
95
|
-
): ReplayEvent[] {
|
|
96
|
-
return events.filter((event) => {
|
|
97
|
-
// Filter by event type
|
|
98
|
-
if (filter.type && !filter.type.includes(event.event_type)) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Filter by agent name (from payload)
|
|
103
|
-
if (filter.agent) {
|
|
104
|
-
const payload = event.payload as any;
|
|
105
|
-
if (payload.agent_name !== filter.agent) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Filter by time range (since)
|
|
111
|
-
if (filter.since) {
|
|
112
|
-
const eventTime = new Date(event.timestamp);
|
|
113
|
-
if (eventTime < filter.since) {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Filter by time range (until)
|
|
119
|
-
if (filter.until) {
|
|
120
|
-
const eventTime = new Date(event.timestamp);
|
|
121
|
-
if (eventTime > filter.until) {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return true;
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ============================================================================
|
|
131
|
-
// replayWithTiming - Async generator with speed control
|
|
132
|
-
// ============================================================================
|
|
133
|
-
|
|
134
|
-
export async function* replayWithTiming(
|
|
135
|
-
events: ReplayEvent[],
|
|
136
|
-
speed: ReplaySpeed,
|
|
137
|
-
): AsyncGenerator<ReplayEvent> {
|
|
138
|
-
if (events.length === 0) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const startTime = Date.now();
|
|
143
|
-
let cumulativeDelay = 0;
|
|
144
|
-
|
|
145
|
-
for (const event of events) {
|
|
146
|
-
// Calculate target time for this event
|
|
147
|
-
cumulativeDelay += event.delta_ms;
|
|
148
|
-
|
|
149
|
-
let targetDelay = cumulativeDelay;
|
|
150
|
-
if (speed === "2x") {
|
|
151
|
-
targetDelay = targetDelay / 2;
|
|
152
|
-
} else if (speed === "instant") {
|
|
153
|
-
targetDelay = 0;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Calculate actual delay needed (accounting for time already elapsed)
|
|
157
|
-
const elapsed = Date.now() - startTime;
|
|
158
|
-
const delay = targetDelay - elapsed;
|
|
159
|
-
|
|
160
|
-
// Wait for the delay if needed (with small buffer for overhead)
|
|
161
|
-
if (delay > 3) {
|
|
162
|
-
// Subtract 3ms buffer to account for async overhead
|
|
163
|
-
const adjustedDelay = delay - 3;
|
|
164
|
-
|
|
165
|
-
// Use Bun.sleep for more precise timing in Bun runtime
|
|
166
|
-
if (typeof Bun !== "undefined" && typeof Bun.sleep === "function") {
|
|
167
|
-
await Bun.sleep(adjustedDelay);
|
|
168
|
-
} else {
|
|
169
|
-
await new Promise((resolve) => setTimeout(resolve, adjustedDelay));
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Yield the event
|
|
174
|
-
yield event;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ============================================================================
|
|
179
|
-
// formatReplayEvent - ANSI colors + box-drawing + relationships
|
|
180
|
-
// ============================================================================
|
|
181
|
-
|
|
182
|
-
export function formatReplayEvent(event: ReplayEvent): string {
|
|
183
|
-
// ANSI color codes
|
|
184
|
-
const colors = {
|
|
185
|
-
DECISION: "\x1b[34m", // Blue
|
|
186
|
-
VIOLATION: "\x1b[31m", // Red
|
|
187
|
-
OUTCOME: "\x1b[32m", // Green
|
|
188
|
-
COMPACTION: "\x1b[33m", // Yellow
|
|
189
|
-
reset: "\x1b[0m",
|
|
190
|
-
gray: "\x1b[90m",
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const color = colors[event.event_type] || colors.reset;
|
|
194
|
-
|
|
195
|
-
// Format timestamp (remove 'Z' suffix and put it BEFORE color codes for regex match)
|
|
196
|
-
const timestamp = new Date(event.timestamp)
|
|
197
|
-
.toISOString()
|
|
198
|
-
.split("T")[1]
|
|
199
|
-
.replace("Z", "");
|
|
200
|
-
const timePrefix = `[${timestamp}]`;
|
|
201
|
-
|
|
202
|
-
// Extract relevant payload fields
|
|
203
|
-
const payload = event.payload as any;
|
|
204
|
-
const beadId = payload.bead_id || "";
|
|
205
|
-
const agentName = payload.agent_name || "";
|
|
206
|
-
const strategyUsed = payload.strategy_used || "";
|
|
207
|
-
const subtaskCount = payload.subtask_count;
|
|
208
|
-
|
|
209
|
-
// Build formatted output with box-drawing characters
|
|
210
|
-
// Put timestamp BEFORE any color codes so regex matches
|
|
211
|
-
let output = `${timePrefix} `;
|
|
212
|
-
output += `${color}┌─ ${event.event_type}${colors.reset}\n`;
|
|
213
|
-
|
|
214
|
-
// Epic relationship
|
|
215
|
-
output += `${colors.gray}│${colors.reset} epic: ${event.epic_id}\n`;
|
|
216
|
-
|
|
217
|
-
// Bead relationship (if present)
|
|
218
|
-
if (beadId) {
|
|
219
|
-
output += `${colors.gray}│${colors.reset} bead: ${beadId}\n`;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Agent (if present)
|
|
223
|
-
if (agentName) {
|
|
224
|
-
output += `${colors.gray}│${colors.reset} agent: ${agentName}\n`;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Strategy (if present)
|
|
228
|
-
if (strategyUsed) {
|
|
229
|
-
output += `${colors.gray}│${colors.reset} strategy: ${strategyUsed}\n`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Subtask count (if present)
|
|
233
|
-
if (subtaskCount !== undefined) {
|
|
234
|
-
output += `${colors.gray}│${colors.reset} subtasks: ${subtaskCount}\n`;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
output += `${colors.gray}└─${colors.reset}`;
|
|
238
|
-
|
|
239
|
-
return output;
|
|
240
|
-
}
|
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Repo Crawl Integration Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests GitHub API wrapper tools against a real public repository.
|
|
5
|
-
* Uses "vercel/next.js" as test target (well-known, stable, public).
|
|
6
|
-
*
|
|
7
|
-
* ## Test Strategy
|
|
8
|
-
* - Happy-path only (error cases covered by unit tests)
|
|
9
|
-
* - Real GitHub API calls (may hit rate limits)
|
|
10
|
-
* - Graceful handling of rate limiting (skip tests if hit)
|
|
11
|
-
* - Fast: minimal API calls, shared test state where safe
|
|
12
|
-
*
|
|
13
|
-
* ## Rate Limit Handling
|
|
14
|
-
* - Unauthenticated: 60 requests/hour
|
|
15
|
-
* - Authenticated: 5000 requests/hour (set GITHUB_TOKEN env var)
|
|
16
|
-
* - Tests check for rate limit errors and skip gracefully
|
|
17
|
-
*
|
|
18
|
-
* ## TDD Note
|
|
19
|
-
* These tests were written FIRST (failing), then tools were verified to pass.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { describe, expect, test } from "bun:test";
|
|
23
|
-
import {
|
|
24
|
-
RepoCrawlError,
|
|
25
|
-
repo_file,
|
|
26
|
-
repo_readme,
|
|
27
|
-
repo_search,
|
|
28
|
-
repo_structure,
|
|
29
|
-
repo_tree,
|
|
30
|
-
} from "./repo-crawl";
|
|
31
|
-
|
|
32
|
-
// Test repository (well-known, stable, public)
|
|
33
|
-
const TEST_REPO = "vercel/next.js";
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Helper to parse JSON response from tool
|
|
37
|
-
*/
|
|
38
|
-
function parseResponse<T>(response: string): T {
|
|
39
|
-
return JSON.parse(response);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
describe("repo_readme", () => {
|
|
45
|
-
test("fetches README.md from public repo", async () => {
|
|
46
|
-
const response = await repo_readme.execute(
|
|
47
|
-
{ repo: TEST_REPO },
|
|
48
|
-
{} as never,
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const result = parseResponse<{
|
|
52
|
-
repo: string;
|
|
53
|
-
path: string;
|
|
54
|
-
content: string;
|
|
55
|
-
size: number;
|
|
56
|
-
truncated: boolean;
|
|
57
|
-
error?: string;
|
|
58
|
-
}>(response);
|
|
59
|
-
|
|
60
|
-
// Skip if rate limited
|
|
61
|
-
if (result.error?.includes("rate limit")) {
|
|
62
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
expect(result.repo).toBe(TEST_REPO);
|
|
67
|
-
expect(result.path).toMatch(/README\.md/i);
|
|
68
|
-
expect(result.content).toContain("Next.js"); // Repo name in README
|
|
69
|
-
expect(result.size).toBeGreaterThan(0);
|
|
70
|
-
expect(typeof result.truncated).toBe("boolean");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("accepts GitHub URLs", async () => {
|
|
74
|
-
const response = await repo_readme.execute(
|
|
75
|
-
{ repo: `https://github.com/${TEST_REPO}` },
|
|
76
|
-
{} as never,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const result = parseResponse<{ repo: string; error?: string }>(response);
|
|
80
|
-
|
|
81
|
-
if (result.error?.includes("rate limit")) {
|
|
82
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
expect(result.repo).toBe(TEST_REPO);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("truncates content when maxLength specified", async () => {
|
|
90
|
-
const response = await repo_readme.execute(
|
|
91
|
-
{ repo: TEST_REPO, maxLength: 100 },
|
|
92
|
-
{} as never,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const result = parseResponse<{
|
|
96
|
-
content: string;
|
|
97
|
-
truncated: boolean;
|
|
98
|
-
error?: string;
|
|
99
|
-
}>(response);
|
|
100
|
-
|
|
101
|
-
if (result.error?.includes("rate limit")) {
|
|
102
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
expect(result.content.length).toBeLessThanOrEqual(125); // Allow for truncation marker + newlines
|
|
107
|
-
expect(result.truncated).toBe(true);
|
|
108
|
-
expect(result.content).toContain("truncated");
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("handles invalid repo gracefully", async () => {
|
|
112
|
-
const response = await repo_readme.execute(
|
|
113
|
-
{ repo: "nonexistent-org/nonexistent-repo-12345" },
|
|
114
|
-
{} as never,
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const result = parseResponse<{ error?: string }>(response);
|
|
118
|
-
|
|
119
|
-
expect(result.error).toBeDefined();
|
|
120
|
-
// Could be rate limit or not found - both are valid error handling
|
|
121
|
-
expect(
|
|
122
|
-
result.error.includes("not found") ||
|
|
123
|
-
result.error.includes("rate limit"),
|
|
124
|
-
).toBe(true);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe("repo_structure", () => {
|
|
129
|
-
test("fetches repo structure with metadata", async () => {
|
|
130
|
-
const response = await repo_structure.execute(
|
|
131
|
-
{ repo: TEST_REPO },
|
|
132
|
-
{} as never,
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const result = parseResponse<{
|
|
136
|
-
repo: string;
|
|
137
|
-
description: string | null;
|
|
138
|
-
language: string | null;
|
|
139
|
-
stars: number;
|
|
140
|
-
topics: string[];
|
|
141
|
-
techStack: string[];
|
|
142
|
-
directories: string[];
|
|
143
|
-
files: string[];
|
|
144
|
-
truncated: boolean;
|
|
145
|
-
error?: string;
|
|
146
|
-
}>(response);
|
|
147
|
-
|
|
148
|
-
if (result.error?.includes("rate limit")) {
|
|
149
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
expect(result.repo).toBe(TEST_REPO);
|
|
154
|
-
expect(result.description).toBeDefined();
|
|
155
|
-
expect(result.stars).toBeGreaterThan(0);
|
|
156
|
-
expect(Array.isArray(result.techStack)).toBe(true);
|
|
157
|
-
expect(result.techStack).toContain("TypeScript"); // Next.js uses TypeScript
|
|
158
|
-
expect(Array.isArray(result.directories)).toBe(true);
|
|
159
|
-
expect(Array.isArray(result.files)).toBe(true);
|
|
160
|
-
expect(typeof result.truncated).toBe("boolean");
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("respects depth parameter", async () => {
|
|
164
|
-
const response = await repo_structure.execute(
|
|
165
|
-
{ repo: TEST_REPO, depth: 1 },
|
|
166
|
-
{} as never,
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
const result = parseResponse<{
|
|
170
|
-
directories: string[];
|
|
171
|
-
files: string[];
|
|
172
|
-
error?: string;
|
|
173
|
-
}>(response);
|
|
174
|
-
|
|
175
|
-
if (result.error?.includes("rate limit")) {
|
|
176
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Depth 1 means no nested paths (no slashes in paths)
|
|
181
|
-
const allPaths = [...result.directories, ...result.files];
|
|
182
|
-
const nestedPaths = allPaths.filter((path) => path.includes("/"));
|
|
183
|
-
expect(nestedPaths.length).toBe(0);
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
describe("repo_tree", () => {
|
|
188
|
-
test("fetches root directory tree", async () => {
|
|
189
|
-
const response = await repo_tree.execute(
|
|
190
|
-
{ repo: TEST_REPO },
|
|
191
|
-
{} as never,
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
const result = parseResponse<{
|
|
195
|
-
repo: string;
|
|
196
|
-
path: string;
|
|
197
|
-
items: Array<{ path: string; type: string; size?: number }>;
|
|
198
|
-
error?: string;
|
|
199
|
-
}>(response);
|
|
200
|
-
|
|
201
|
-
if (result.error?.includes("rate limit")) {
|
|
202
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
expect(result.repo).toBe(TEST_REPO);
|
|
207
|
-
expect(result.path).toBe("(root)");
|
|
208
|
-
expect(Array.isArray(result.items)).toBe(true);
|
|
209
|
-
expect(result.items.length).toBeGreaterThan(0);
|
|
210
|
-
|
|
211
|
-
// Should have both files and directories
|
|
212
|
-
const types = new Set(result.items.map((item) => item.type));
|
|
213
|
-
expect(types.has("file") || types.has("dir")).toBe(true);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("fetches specific directory tree", async () => {
|
|
217
|
-
const response = await repo_tree.execute(
|
|
218
|
-
{ repo: TEST_REPO, path: "packages" },
|
|
219
|
-
{} as never,
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
const result = parseResponse<{
|
|
223
|
-
path: string;
|
|
224
|
-
items: Array<{ path: string; type: string }>;
|
|
225
|
-
error?: string;
|
|
226
|
-
}>(response);
|
|
227
|
-
|
|
228
|
-
if (result.error?.includes("rate limit")) {
|
|
229
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
expect(result.path).toBe("packages");
|
|
234
|
-
expect(Array.isArray(result.items)).toBe(true);
|
|
235
|
-
|
|
236
|
-
// All items should be under packages/ path
|
|
237
|
-
for (const item of result.items) {
|
|
238
|
-
expect(item.path).toMatch(/^packages\//);
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test("handles file path gracefully", async () => {
|
|
243
|
-
const response = await repo_tree.execute(
|
|
244
|
-
{ repo: TEST_REPO, path: "package.json" },
|
|
245
|
-
{} as never,
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
const result = parseResponse<{ error?: string }>(response);
|
|
249
|
-
|
|
250
|
-
expect(result.error).toBeDefined();
|
|
251
|
-
// Could be rate limit or "not a directory" - both are valid error handling
|
|
252
|
-
expect(
|
|
253
|
-
result.error.includes("not a directory") ||
|
|
254
|
-
result.error.includes("rate limit"),
|
|
255
|
-
).toBe(true);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
describe("repo_file", () => {
|
|
260
|
-
test("fetches file content", async () => {
|
|
261
|
-
const response = await repo_file.execute(
|
|
262
|
-
{ repo: TEST_REPO, path: "package.json" },
|
|
263
|
-
{} as never,
|
|
264
|
-
);
|
|
265
|
-
|
|
266
|
-
const result = parseResponse<{
|
|
267
|
-
repo: string;
|
|
268
|
-
path: string;
|
|
269
|
-
content: string;
|
|
270
|
-
size: number;
|
|
271
|
-
truncated: boolean;
|
|
272
|
-
error?: string;
|
|
273
|
-
}>(response);
|
|
274
|
-
|
|
275
|
-
if (result.error?.includes("rate limit")) {
|
|
276
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
expect(result.repo).toBe(TEST_REPO);
|
|
281
|
-
expect(result.path).toBe("package.json");
|
|
282
|
-
expect(result.content).toContain('"name"'); // Valid package.json
|
|
283
|
-
expect(result.size).toBeGreaterThan(0);
|
|
284
|
-
expect(typeof result.truncated).toBe("boolean");
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
test("truncates large files when maxLength specified", async () => {
|
|
288
|
-
const response = await repo_file.execute(
|
|
289
|
-
{ repo: TEST_REPO, path: "package.json", maxLength: 50 },
|
|
290
|
-
{} as never,
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
const result = parseResponse<{
|
|
294
|
-
content: string;
|
|
295
|
-
truncated: boolean;
|
|
296
|
-
error?: string;
|
|
297
|
-
}>(response);
|
|
298
|
-
|
|
299
|
-
if (result.error?.includes("rate limit")) {
|
|
300
|
-
console.warn("⚠️ Skipping test: GitHub API rate limit hit");
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
expect(result.content.length).toBeLessThanOrEqual(75); // Allow for truncation marker + newlines
|
|
305
|
-
expect(result.truncated).toBe(true);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
test("handles directory path gracefully", async () => {
|
|
309
|
-
const response = await repo_file.execute(
|
|
310
|
-
{ repo: TEST_REPO, path: "packages" },
|
|
311
|
-
{} as never,
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
const result = parseResponse<{ error?: string }>(response);
|
|
315
|
-
|
|
316
|
-
expect(result.error).toBeDefined();
|
|
317
|
-
// Could be rate limit or "not a file" - both are valid error handling
|
|
318
|
-
expect(
|
|
319
|
-
result.error.includes("not a file") ||
|
|
320
|
-
result.error.includes("rate limit"),
|
|
321
|
-
).toBe(true);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
test("handles nonexistent file gracefully", async () => {
|
|
325
|
-
const response = await repo_file.execute(
|
|
326
|
-
{ repo: TEST_REPO, path: "nonexistent-file-12345.txt" },
|
|
327
|
-
{} as never,
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
const result = parseResponse<{ error?: string }>(response);
|
|
331
|
-
|
|
332
|
-
expect(result.error).toBeDefined();
|
|
333
|
-
// Could be rate limit or not found - both are valid error handling
|
|
334
|
-
expect(
|
|
335
|
-
result.error.includes("not found") ||
|
|
336
|
-
result.error.includes("rate limit"),
|
|
337
|
-
).toBe(true);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
describe("repo_search", () => {
|
|
342
|
-
test("searches code in repo", async () => {
|
|
343
|
-
const response = await repo_search.execute(
|
|
344
|
-
{ repo: TEST_REPO, query: "useRouter" },
|
|
345
|
-
{} as never,
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
const result = parseResponse<{
|
|
349
|
-
repo: string;
|
|
350
|
-
query: string;
|
|
351
|
-
totalCount: number;
|
|
352
|
-
results: Array<{
|
|
353
|
-
path: string;
|
|
354
|
-
url: string;
|
|
355
|
-
matches: string[];
|
|
356
|
-
}>;
|
|
357
|
-
error?: string;
|
|
358
|
-
}>(response);
|
|
359
|
-
|
|
360
|
-
// GitHub Code Search API requires authentication for most repos
|
|
361
|
-
if (
|
|
362
|
-
result.error?.includes("rate limit") ||
|
|
363
|
-
result.error?.includes("secondary rate limit")
|
|
364
|
-
) {
|
|
365
|
-
console.warn(
|
|
366
|
-
"⚠️ Skipping test: GitHub API rate limit hit (set GITHUB_TOKEN for higher limits)",
|
|
367
|
-
);
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// If there's any error, log it and skip
|
|
372
|
-
if (result.error) {
|
|
373
|
-
console.warn(`⚠️ Skipping test: ${result.error}`);
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
expect(result.repo).toBe(TEST_REPO);
|
|
378
|
-
expect(result.query).toBe("useRouter");
|
|
379
|
-
expect(result.totalCount).toBeGreaterThan(0);
|
|
380
|
-
expect(Array.isArray(result.results)).toBe(true);
|
|
381
|
-
|
|
382
|
-
// First result should have required fields
|
|
383
|
-
if (result.results.length > 0) {
|
|
384
|
-
const firstResult = result.results[0];
|
|
385
|
-
expect(firstResult.path).toBeDefined();
|
|
386
|
-
expect(firstResult.url).toContain("github.com");
|
|
387
|
-
expect(Array.isArray(firstResult.matches)).toBe(true);
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test("respects maxResults parameter", async () => {
|
|
392
|
-
const response = await repo_search.execute(
|
|
393
|
-
{ repo: TEST_REPO, query: "useRouter", maxResults: 3 },
|
|
394
|
-
{} as never,
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
const result = parseResponse<{
|
|
398
|
-
results: Array<{ path: string }>;
|
|
399
|
-
error?: string;
|
|
400
|
-
}>(response);
|
|
401
|
-
|
|
402
|
-
if (result.error) {
|
|
403
|
-
console.warn(`⚠️ Skipping test: ${result.error}`);
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
expect(result.results.length).toBeLessThanOrEqual(3);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test("handles no results gracefully", async () => {
|
|
411
|
-
const response = await repo_search.execute(
|
|
412
|
-
{ repo: TEST_REPO, query: "zzz-nonexistent-query-12345-zzz" },
|
|
413
|
-
{} as never,
|
|
414
|
-
);
|
|
415
|
-
|
|
416
|
-
const result = parseResponse<{
|
|
417
|
-
totalCount: number;
|
|
418
|
-
results: Array<unknown>;
|
|
419
|
-
error?: string;
|
|
420
|
-
}>(response);
|
|
421
|
-
|
|
422
|
-
if (result.error) {
|
|
423
|
-
console.warn(`⚠️ Skipping test: ${result.error}`);
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
expect(result.totalCount).toBe(0);
|
|
428
|
-
expect(result.results.length).toBe(0);
|
|
429
|
-
});
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
describe("RepoCrawlError", () => {
|
|
433
|
-
test("has correct properties", () => {
|
|
434
|
-
const error = new RepoCrawlError("Test error", 404, "/test/endpoint");
|
|
435
|
-
|
|
436
|
-
expect(error.message).toBe("Test error");
|
|
437
|
-
expect(error.statusCode).toBe(404);
|
|
438
|
-
expect(error.endpoint).toBe("/test/endpoint");
|
|
439
|
-
expect(error.name).toBe("RepoCrawlError");
|
|
440
|
-
});
|
|
441
|
-
});
|