@sparkleideas/claims 3.5.2-patch.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/src/index.ts ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @sparkleideas/claims (ADR-016)
3
+ *
4
+ * Issue claiming and handoff management for human and agent collaboration.
5
+ *
6
+ * Features:
7
+ * - Issue claiming and releasing
8
+ * - Human-to-agent and agent-to-agent handoffs
9
+ * - Status tracking and updates (active, paused, handoff-pending, review-requested, blocked, stealable, completed)
10
+ * - Auto-management (expiration, auto-assignment)
11
+ * - Work stealing with contest windows
12
+ * - Load balancing and swarm rebalancing
13
+ * - Full event sourcing (ADR-007)
14
+ *
15
+ * MCP Tools (17 total):
16
+ * - Core Claiming (7): claim, release, handoff, status_update, list_available, list_mine, board
17
+ * - Work Stealing (4): mark_stealable, steal, get_stealable, contest_steal
18
+ * - Load Balancing (3): agent_load_info, swarm_rebalance, swarm_load_overview
19
+ * - Additional (3): claim_history, claim_metrics, claim_config
20
+ *
21
+ * ADR-016 Types:
22
+ * - ClaimStatus: active | paused | handoff-pending | review-requested | blocked | stealable | completed
23
+ * - ClaimantType: human | agent
24
+ * - StealReason: timeout | overloaded | blocked | voluntary | rebalancing | abandoned | priority-change
25
+ * - HandoffReason: capacity | expertise | shift-change | escalation | voluntary | rebalancing
26
+ *
27
+ * @module v3/claims
28
+ */
29
+
30
+ // Domain layer - Types, Events, Rules, Repositories
31
+ export * from './domain/index.js';
32
+
33
+ // Application layer - Services
34
+ export * from './application/index.js';
35
+
36
+ // Infrastructure layer - Persistence
37
+ export * from './infrastructure/index.js';
38
+
39
+ // API layer - MCP Tools
40
+ export {
41
+ // All tools collection
42
+ claimsTools,
43
+
44
+ // Tool categories
45
+ coreClaimingTools,
46
+ workStealingTools,
47
+ loadBalancingTools,
48
+ additionalClaimsTools,
49
+
50
+ // Core Claiming Tools (7)
51
+ issueClaimTool,
52
+ issueReleaseTool,
53
+ issueHandoffTool,
54
+ issueStatusUpdateTool,
55
+ issueListAvailableTool,
56
+ issueListMineTool,
57
+ issueBoardTool,
58
+
59
+ // Work Stealing Tools (4)
60
+ issueMarkStealableTool,
61
+ issueStealTool,
62
+ issueGetStealableTool,
63
+ issueContestStealTool,
64
+
65
+ // Load Balancing Tools (3)
66
+ agentLoadInfoTool,
67
+ swarmRebalanceTool,
68
+ swarmLoadOverviewTool,
69
+
70
+ // Additional Tools (3)
71
+ claimHistoryTool,
72
+ claimMetricsTool,
73
+ claimConfigTool,
74
+
75
+ // Utility functions
76
+ registerClaimsTools,
77
+ getClaimsToolsByCategory,
78
+ getClaimsToolByName,
79
+ } from './api/mcp-tools.js';
@@ -0,0 +1,358 @@
1
+ /**
2
+ * @sparkleideas/claims - Claim Repository Implementation
3
+ * SQLite-based persistence for claims (ADR-016)
4
+ *
5
+ * @module v3/claims/infrastructure/claim-repository
6
+ */
7
+
8
+ import {
9
+ ClaimId,
10
+ IssueId,
11
+ Claimant,
12
+ ClaimStatus,
13
+ IssueClaim,
14
+ IssueClaimWithStealing,
15
+ AgentType,
16
+ ExtendedClaimStatus,
17
+ ExtendedIssueClaim,
18
+ ClaimQueryOptions,
19
+ ClaimStatistics,
20
+ } from '../domain/types.js';
21
+ import { IClaimRepository } from '../domain/repositories.js';
22
+ import { IIssueClaimRepository } from '../domain/types.js';
23
+
24
+ // =============================================================================
25
+ // In-Memory Claim Repository (Default Implementation)
26
+ // =============================================================================
27
+
28
+ /**
29
+ * In-memory implementation of the claim repository
30
+ * Suitable for development and testing
31
+ */
32
+ export class InMemoryClaimRepository implements IClaimRepository, IIssueClaimRepository {
33
+ private claims: Map<ClaimId, IssueClaimWithStealing> = new Map();
34
+ private issueIndex: Map<string, ClaimId> = new Map(); // issueId:repo -> claimId
35
+ private claimantIndex: Map<string, Set<ClaimId>> = new Map(); // claimantId -> claimIds
36
+
37
+ async initialize(): Promise<void> {
38
+ // No initialization needed for in-memory store
39
+ }
40
+
41
+ async shutdown(): Promise<void> {
42
+ this.claims.clear();
43
+ this.issueIndex.clear();
44
+ this.claimantIndex.clear();
45
+ }
46
+
47
+ // ==========================================================================
48
+ // CRUD Operations
49
+ // ==========================================================================
50
+
51
+ async save(claim: IssueClaim | IssueClaimWithStealing): Promise<void> {
52
+ const fullClaim = this.ensureFullClaim(claim);
53
+ this.claims.set(claim.id, fullClaim);
54
+
55
+ // Update indexes
56
+ const issueKey = this.getIssueKey(claim.issueId, (claim as any).repository ?? '');
57
+ this.issueIndex.set(issueKey, claim.id);
58
+
59
+ const claimantId = claim.claimant.id;
60
+ if (!this.claimantIndex.has(claimantId)) {
61
+ this.claimantIndex.set(claimantId, new Set());
62
+ }
63
+ this.claimantIndex.get(claimantId)!.add(claim.id);
64
+ }
65
+
66
+ async update(claim: IssueClaimWithStealing): Promise<void> {
67
+ await this.save(claim);
68
+ }
69
+
70
+ async findById(claimId: ClaimId): Promise<IssueClaimWithStealing | null> {
71
+ return this.claims.get(claimId) ?? null;
72
+ }
73
+
74
+ async findByIssueId(issueId: IssueId, repository?: string): Promise<IssueClaimWithStealing | null> {
75
+ const issueKey = this.getIssueKey(issueId, repository ?? '');
76
+ const claimId = this.issueIndex.get(issueKey);
77
+ if (!claimId) return null;
78
+
79
+ const claim = this.claims.get(claimId);
80
+ if (!claim) return null;
81
+
82
+ // Return only if claim is active
83
+ if (this.isActiveStatus(claim.status)) {
84
+ return claim;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ async findByClaimant(claimant: Claimant): Promise<IssueClaim[]> {
90
+ return this.findByAgentId(claimant.id);
91
+ }
92
+
93
+ async findByAgentId(agentId: string): Promise<IssueClaimWithStealing[]> {
94
+ const claimIds = this.claimantIndex.get(agentId);
95
+ if (!claimIds) return [];
96
+
97
+ return Array.from(claimIds)
98
+ .map((id) => this.claims.get(id))
99
+ .filter((c): c is IssueClaimWithStealing => c !== undefined);
100
+ }
101
+
102
+ async findByStatus(status: ClaimStatus): Promise<IssueClaim[]> {
103
+ return Array.from(this.claims.values()).filter((c) => c.status === status);
104
+ }
105
+
106
+ async findStealable(agentType?: AgentType): Promise<IssueClaimWithStealing[]> {
107
+ return Array.from(this.claims.values()).filter((c) => {
108
+ if (c.status !== 'stealable') return false;
109
+ if (!c.stealInfo) return true;
110
+ if (!agentType) return true;
111
+ if (!c.stealInfo.allowedStealerTypes) return true;
112
+ return c.stealInfo.allowedStealerTypes.includes(agentType);
113
+ });
114
+ }
115
+
116
+ async findContested(): Promise<IssueClaimWithStealing[]> {
117
+ return Array.from(this.claims.values()).filter(
118
+ (c) => c.contestInfo && !c.contestInfo.resolution
119
+ );
120
+ }
121
+
122
+ async findAll(): Promise<IssueClaimWithStealing[]> {
123
+ return Array.from(this.claims.values());
124
+ }
125
+
126
+ async delete(claimId: ClaimId): Promise<void> {
127
+ const claim = this.claims.get(claimId);
128
+ if (claim) {
129
+ // Remove from indexes
130
+ const issueKey = this.getIssueKey(claim.issueId, (claim as any).repository ?? '');
131
+ this.issueIndex.delete(issueKey);
132
+
133
+ const claimantId = claim.claimant.id;
134
+ const claimantClaims = this.claimantIndex.get(claimantId);
135
+ if (claimantClaims) {
136
+ claimantClaims.delete(claimId);
137
+ if (claimantClaims.size === 0) {
138
+ this.claimantIndex.delete(claimantId);
139
+ }
140
+ }
141
+
142
+ this.claims.delete(claimId);
143
+ }
144
+ }
145
+
146
+ // ==========================================================================
147
+ // Query Operations
148
+ // ==========================================================================
149
+
150
+ async findActiveClaims(): Promise<IssueClaim[]> {
151
+ return Array.from(this.claims.values()).filter((c) =>
152
+ this.isActiveStatus(c.status)
153
+ );
154
+ }
155
+
156
+ async findStaleClaims(staleSince: Date): Promise<IssueClaim[]> {
157
+ const staleTimestamp = staleSince.getTime();
158
+ return Array.from(this.claims.values()).filter(
159
+ (c) =>
160
+ this.isActiveStatus(c.status) &&
161
+ c.lastActivityAt.getTime() < staleTimestamp
162
+ );
163
+ }
164
+
165
+ async findClaimsWithPendingHandoffs(): Promise<IssueClaim[]> {
166
+ return Array.from(this.claims.values()).filter(
167
+ (c) => c.status === 'pending_handoff'
168
+ );
169
+ }
170
+
171
+ async countByClaimant(claimantId: string): Promise<number> {
172
+ return this.claimantIndex.get(claimantId)?.size ?? 0;
173
+ }
174
+
175
+ async countByAgentId(agentId: string): Promise<number> {
176
+ const claims = await this.findByAgentId(agentId);
177
+ return claims.filter((c) => this.isActiveStatus(c.status)).length;
178
+ }
179
+
180
+ // ==========================================================================
181
+ // Extended Query Operations (ADR-016)
182
+ // ==========================================================================
183
+
184
+ async query(options: ClaimQueryOptions): Promise<IssueClaimWithStealing[]> {
185
+ let results = Array.from(this.claims.values());
186
+
187
+ // Apply filters
188
+ if (options.claimantId) {
189
+ results = results.filter((c) => c.claimant.id === options.claimantId);
190
+ }
191
+
192
+ if (options.claimantType) {
193
+ results = results.filter((c) => c.claimant.type === options.claimantType);
194
+ }
195
+
196
+ if (options.status) {
197
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
198
+ results = results.filter((c) => statuses.includes(c.status as any));
199
+ }
200
+
201
+ if (options.repository) {
202
+ results = results.filter((c) => (c as any).repository === options.repository);
203
+ }
204
+
205
+ if (options.issueId) {
206
+ results = results.filter((c) => c.issueId === options.issueId);
207
+ }
208
+
209
+ if (options.stealableOnly) {
210
+ results = results.filter((c) => c.status === 'stealable');
211
+ }
212
+
213
+ if (options.blockedOnly) {
214
+ results = results.filter((c) => c.blockedReason !== undefined);
215
+ }
216
+
217
+ if (options.createdAfter) {
218
+ results = results.filter((c) => c.claimedAt.getTime() >= options.createdAfter!);
219
+ }
220
+
221
+ if (options.createdBefore) {
222
+ results = results.filter((c) => c.claimedAt.getTime() <= options.createdBefore!);
223
+ }
224
+
225
+ if (options.updatedAfter) {
226
+ results = results.filter((c) => c.lastActivityAt.getTime() >= options.updatedAfter!);
227
+ }
228
+
229
+ // Apply sorting
230
+ if (options.sortBy) {
231
+ results.sort((a, b) => {
232
+ let aVal: number, bVal: number;
233
+
234
+ switch (options.sortBy) {
235
+ case 'claimedAt':
236
+ aVal = a.claimedAt.getTime();
237
+ bVal = b.claimedAt.getTime();
238
+ break;
239
+ case 'updatedAt':
240
+ aVal = a.lastActivityAt.getTime();
241
+ bVal = b.lastActivityAt.getTime();
242
+ break;
243
+ case 'progress':
244
+ aVal = a.progress;
245
+ bVal = b.progress;
246
+ break;
247
+ default:
248
+ return 0;
249
+ }
250
+
251
+ return options.sortDirection === 'desc' ? bVal - aVal : aVal - bVal;
252
+ });
253
+ }
254
+
255
+ // Apply pagination
256
+ if (options.offset) {
257
+ results = results.slice(options.offset);
258
+ }
259
+
260
+ if (options.limit) {
261
+ results = results.slice(0, options.limit);
262
+ }
263
+
264
+ return results;
265
+ }
266
+
267
+ async getStatistics(): Promise<ClaimStatistics> {
268
+ const claims = Array.from(this.claims.values());
269
+
270
+ const byStatus: Record<string, number> = {
271
+ active: 0,
272
+ paused: 0,
273
+ 'handoff-pending': 0,
274
+ 'review-requested': 0,
275
+ blocked: 0,
276
+ stealable: 0,
277
+ completed: 0,
278
+ };
279
+
280
+ const byClaimantType: Record<string, number> = {
281
+ human: 0,
282
+ agent: 0,
283
+ };
284
+
285
+ const byRepository: Record<string, number> = {};
286
+ let totalDuration = 0;
287
+ let completedCount = 0;
288
+ let totalProgress = 0;
289
+ const now = Date.now();
290
+ const last24h = now - 24 * 60 * 60 * 1000;
291
+
292
+ for (const claim of claims) {
293
+ byStatus[claim.status] = (byStatus[claim.status] ?? 0) + 1;
294
+ byClaimantType[claim.claimant.type] = (byClaimantType[claim.claimant.type] ?? 0) + 1;
295
+
296
+ const repo = (claim as any).repository ?? 'unknown';
297
+ byRepository[repo] = (byRepository[repo] ?? 0) + 1;
298
+
299
+ totalProgress += claim.progress;
300
+
301
+ if (claim.status === 'completed') {
302
+ completedCount++;
303
+ const duration = claim.lastActivityAt.getTime() - claim.claimedAt.getTime();
304
+ totalDuration += duration;
305
+ }
306
+ }
307
+
308
+ const completedLast24h = claims.filter(
309
+ (c) => c.status === 'completed' && c.lastActivityAt.getTime() >= last24h
310
+ ).length;
311
+
312
+ return {
313
+ totalClaims: claims.length,
314
+ byStatus: byStatus as any,
315
+ byPriority: { critical: 0, high: 0, medium: 0, low: 0 },
316
+ byClaimantType: byClaimantType as any,
317
+ avgDurationMs: completedCount > 0 ? totalDuration / completedCount : 0,
318
+ avgProgress: claims.length > 0 ? totalProgress / claims.length : 0,
319
+ activeSteals: claims.filter((c) => c.status === 'stealable').length,
320
+ pendingHandoffs: claims.filter((c) => c.status === 'pending_handoff').length,
321
+ completedLast24h,
322
+ byRepository,
323
+ };
324
+ }
325
+
326
+ // ==========================================================================
327
+ // Helper Methods
328
+ // ==========================================================================
329
+
330
+ private getIssueKey(issueId: IssueId, repository: string): string {
331
+ return `${repository}:${issueId}`;
332
+ }
333
+
334
+ private isActiveStatus(status: string): boolean {
335
+ return ['active', 'paused', 'blocked', 'pending_handoff', 'in_review', 'stealable'].includes(
336
+ status
337
+ );
338
+ }
339
+
340
+ private ensureFullClaim(claim: IssueClaim | IssueClaimWithStealing): IssueClaimWithStealing {
341
+ const fullClaim = claim as IssueClaimWithStealing;
342
+ if (fullClaim.progress === undefined) {
343
+ (fullClaim as any).progress = 0;
344
+ }
345
+ return fullClaim;
346
+ }
347
+ }
348
+
349
+ // =============================================================================
350
+ // Factory Function
351
+ // =============================================================================
352
+
353
+ /**
354
+ * Create a new claim repository
355
+ */
356
+ export function createClaimRepository(): InMemoryClaimRepository {
357
+ return new InMemoryClaimRepository();
358
+ }